jbangert-bindata 1.5.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.
- data/.gitignore +1 -0
- data/BSDL +22 -0
- data/COPYING +52 -0
- data/ChangeLog.rdoc +204 -0
- data/Gemfile +2 -0
- data/INSTALL +11 -0
- data/NEWS.rdoc +164 -0
- data/README.md +54 -0
- data/Rakefile +13 -0
- data/bindata.gemspec +31 -0
- data/doc/manual.haml +407 -0
- data/doc/manual.md +1649 -0
- data/examples/NBT.txt +149 -0
- data/examples/gzip.rb +161 -0
- data/examples/ip_address.rb +22 -0
- data/examples/list.rb +124 -0
- data/examples/nbt.rb +178 -0
- data/lib/bindata.rb +33 -0
- data/lib/bindata/alignment.rb +83 -0
- data/lib/bindata/array.rb +335 -0
- data/lib/bindata/base.rb +388 -0
- data/lib/bindata/base_primitive.rb +214 -0
- data/lib/bindata/bits.rb +87 -0
- data/lib/bindata/choice.rb +216 -0
- data/lib/bindata/count_bytes_remaining.rb +35 -0
- data/lib/bindata/deprecated.rb +50 -0
- data/lib/bindata/dsl.rb +312 -0
- data/lib/bindata/float.rb +80 -0
- data/lib/bindata/int.rb +184 -0
- data/lib/bindata/io.rb +274 -0
- data/lib/bindata/lazy.rb +105 -0
- data/lib/bindata/offset.rb +91 -0
- data/lib/bindata/params.rb +135 -0
- data/lib/bindata/primitive.rb +135 -0
- data/lib/bindata/record.rb +110 -0
- data/lib/bindata/registry.rb +92 -0
- data/lib/bindata/rest.rb +35 -0
- data/lib/bindata/sanitize.rb +290 -0
- data/lib/bindata/skip.rb +48 -0
- data/lib/bindata/string.rb +145 -0
- data/lib/bindata/stringz.rb +96 -0
- data/lib/bindata/struct.rb +388 -0
- data/lib/bindata/trace.rb +94 -0
- data/lib/bindata/version.rb +3 -0
- data/setup.rb +1585 -0
- data/spec/alignment_spec.rb +61 -0
- data/spec/array_spec.rb +331 -0
- data/spec/base_primitive_spec.rb +238 -0
- data/spec/base_spec.rb +376 -0
- data/spec/bits_spec.rb +163 -0
- data/spec/choice_spec.rb +263 -0
- data/spec/count_bytes_remaining_spec.rb +38 -0
- data/spec/deprecated_spec.rb +31 -0
- data/spec/example.rb +21 -0
- data/spec/float_spec.rb +37 -0
- data/spec/int_spec.rb +216 -0
- data/spec/io_spec.rb +352 -0
- data/spec/lazy_spec.rb +217 -0
- data/spec/primitive_spec.rb +202 -0
- data/spec/record_spec.rb +530 -0
- data/spec/registry_spec.rb +108 -0
- data/spec/rest_spec.rb +26 -0
- data/spec/skip_spec.rb +27 -0
- data/spec/spec_common.rb +58 -0
- data/spec/string_spec.rb +300 -0
- data/spec/stringz_spec.rb +118 -0
- data/spec/struct_spec.rb +350 -0
- data/spec/system_spec.rb +380 -0
- data/tasks/manual.rake +36 -0
- data/tasks/rspec.rake +17 -0
- metadata +208 -0
data/examples/nbt.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
|
3
|
+
# An example reader for Minecraft's NBT format.
|
4
|
+
# http://www.minecraft.net/docs/NBT.txt
|
5
|
+
#
|
6
|
+
# This is an example of how to write a BinData
|
7
|
+
# declaration for a recursively defined file format.
|
8
|
+
module Nbt
|
9
|
+
|
10
|
+
TAG_NAMES = {
|
11
|
+
0 => "End",
|
12
|
+
1 => "Byte",
|
13
|
+
2 => "Short",
|
14
|
+
3 => "Int",
|
15
|
+
4 => "Long",
|
16
|
+
5 => "Float",
|
17
|
+
6 => "Double",
|
18
|
+
7 => "Byte_Array",
|
19
|
+
8 => "String",
|
20
|
+
9 => "List",
|
21
|
+
10 => "Compound"
|
22
|
+
}
|
23
|
+
|
24
|
+
# NBT.txt line 25
|
25
|
+
class TagEnd < BinData::Primitive
|
26
|
+
def get; ""; end
|
27
|
+
def set(v); end
|
28
|
+
|
29
|
+
def to_formatted_s(indent = 0); to_s; end
|
30
|
+
end
|
31
|
+
|
32
|
+
# NBT.txt line 31
|
33
|
+
class TagByte < BinData::Int8
|
34
|
+
def to_formatted_s(indent = 0); to_s; end
|
35
|
+
end
|
36
|
+
|
37
|
+
# NBT.txt line 34
|
38
|
+
class TagShort < BinData::Int16be
|
39
|
+
def to_formatted_s(indent = 0); to_s; end
|
40
|
+
end
|
41
|
+
|
42
|
+
# NBT.txt line 37
|
43
|
+
class TagInt < BinData::Int32be
|
44
|
+
def to_formatted_s(indent = 0); to_s; end
|
45
|
+
end
|
46
|
+
|
47
|
+
# NBT.txt line 40
|
48
|
+
class TagLong < BinData::Int64be
|
49
|
+
def to_formatted_s(indent = 0); to_s; end
|
50
|
+
end
|
51
|
+
|
52
|
+
# NBT.txt line 43
|
53
|
+
class TagFloat < BinData::FloatBe
|
54
|
+
def to_formatted_s(indent = 0); to_s; end
|
55
|
+
end
|
56
|
+
|
57
|
+
# NBT.txt line 46
|
58
|
+
class TagDouble < BinData::DoubleBe
|
59
|
+
def to_formatted_s(indent = 0); to_s; end
|
60
|
+
end
|
61
|
+
|
62
|
+
# NBT.txt line 49
|
63
|
+
class TagByteArray < BinData::Record
|
64
|
+
int32be :len, :value => lambda { data.length }
|
65
|
+
string :data, :read_length => :len
|
66
|
+
|
67
|
+
def to_formatted_s(indent = 0)
|
68
|
+
"[#{len} bytes]"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# NBT.txt line 53
|
73
|
+
class TagString < BinData::Primitive
|
74
|
+
int16be :len, :value => lambda { data.length }
|
75
|
+
string :data, :read_length => :len
|
76
|
+
|
77
|
+
def get
|
78
|
+
self.data
|
79
|
+
end
|
80
|
+
|
81
|
+
def set(v)
|
82
|
+
self.data = v
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_formatted_s(indent = 0); to_s; end
|
86
|
+
end
|
87
|
+
|
88
|
+
## Payload is the most important class to understand.
|
89
|
+
## This abstraction allows recursive formats.
|
90
|
+
## eg. lists can contain lists can contain lists.
|
91
|
+
|
92
|
+
# Forward references used by Payload
|
93
|
+
class TagCompound < BinData::Record; end
|
94
|
+
class TagList < BinData::Record; end
|
95
|
+
|
96
|
+
# NBT.txt line 10
|
97
|
+
class Payload < BinData::Choice
|
98
|
+
tag_end 0
|
99
|
+
tag_byte 1
|
100
|
+
tag_short 2
|
101
|
+
tag_int 3
|
102
|
+
tag_long 4
|
103
|
+
tag_float 5
|
104
|
+
tag_double 6
|
105
|
+
tag_byte_array 7
|
106
|
+
tag_string 8
|
107
|
+
tag_list 9
|
108
|
+
tag_compound 10
|
109
|
+
end
|
110
|
+
|
111
|
+
# NBT.txt line 6, 27
|
112
|
+
class NamedTag < BinData::Record
|
113
|
+
int8 :tag_id
|
114
|
+
tag_string :name, :onlyif => :not_end_tag?
|
115
|
+
payload :payload, :onlyif => :not_end_tag?, :selection => :tag_id
|
116
|
+
|
117
|
+
def not_end_tag?
|
118
|
+
tag_id != 0
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_formatted_s(indent = 0)
|
122
|
+
" " * indent +
|
123
|
+
"TAG_#{TAG_NAMES[tag_id]}(\"#{name}\"): " +
|
124
|
+
payload.to_formatted_s(indent) + "\n"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# NBT.txt line 57
|
129
|
+
class TagList < BinData::Record
|
130
|
+
int8 :tag_id
|
131
|
+
int32be :len, :value => lambda { data.length }
|
132
|
+
array :data, :initial_length => :len do
|
133
|
+
payload :selection => :tag_id
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_formatted_s(indent = 0)
|
137
|
+
pre = " " * indent
|
138
|
+
tag_type = "TAG_#{TAG_NAMES[tag_id]}"
|
139
|
+
|
140
|
+
"#{len} entries of type #{tag_type}\n" +
|
141
|
+
pre + "{\n" +
|
142
|
+
data.collect { |el| " #{pre}#{tag_type}: #{el.to_formatted_s(indent + 1)}\n" }.join("") +
|
143
|
+
pre + "}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# NBT.txt line 63
|
148
|
+
class TagCompound < BinData::Record
|
149
|
+
array :data, :read_until => lambda { element.tag_id == 0 } do
|
150
|
+
named_tag
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_formatted_s(indent = 0)
|
154
|
+
pre = " " * indent
|
155
|
+
"#{data.length - 1} entries\n" +
|
156
|
+
pre + "{\n" +
|
157
|
+
data[0..-2].collect { |el| el.to_formatted_s(indent + 1) }.join("") +
|
158
|
+
pre + "}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# NBT.txt line 3
|
163
|
+
class Nbt < NamedTag
|
164
|
+
def self.read(io)
|
165
|
+
require 'zlib'
|
166
|
+
super(Zlib::GzipReader.new(io))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
if $0 == __FILE__
|
172
|
+
require 'stringio'
|
173
|
+
|
174
|
+
bigtest_nbt = StringIO.new "\037\213\b\000\000\000\000\000\000\003\355T\317O\032A\024~\302\002\313\226\202\261\304\020c\314\253\265\204\245\333\315B\021\211\261\210\026,\232\r\032\330\2501\206\270+\303\202.\273fw\260\361\324K{lz\353?\323#\177C\317\275\366\277\240\303/{i\317\275\3602\311\367\346\275o\346{o&y\002\004TrO,\016x\313\261M\215x\364\343pb>\b{\035\307\245\223\030\017\202G\335\356\204\002b\265\242\252\307xv\\W\313\250U\017\e\310\326\036j\225\206\206\r\255~X{\217\203\317\203O\203o\317\003\020n[\216>\276\2458Ld\375\020\352\332t\246\#@\334f.i\341\265\323\273s\372v\v)\333\v\340\357\350=\0368[\357\021\bV\365\336]\337\v@\340^\267\372d\267\004\000\214ALs\306\bUL\323 .}\244\300\310\302\020\263\272\336X\vS\243\356D\216E\0030\261'S\214L\361\351\024\243S\214\205\341\331\237\343\263\362D\201\245|3\335\330\273\307\252u\023_(\034\b\327.\321Y?\257\035\e`!Y\337\372\361\005\376\301\316\374\235\275\000\274\361@\311\370\205B@F\376\236\353\352\017\223:h\207`\273\35327\243(\n\216\273\365\320ic\312N\333\351\354\346\346+;\275%\276dI\t=\252\273\224\375\030~\350\322\016\332o\025L\261h>+\341\233\234\204\231\274\204\005\teY\026E\000\377/(\256/\362\302\262\244.\035 wZ;\271\214\312\347)\337QA\311\026\265\305m\241*\255,\3051\177\272z\222\216^\235_\370\022\005#\e\321\366\267w\252\315\225r\274\236\337X]K\227\256\222\027\271D\320\200\310\372>\277\263\334T\313\aun\243\266vY\222\223\251\334QP\231k\3145\346\032\377W#\bB\313\351\e\326x\302\354\376\374z\373}x\323\204\337\324\362\244\373\b\006\000\000"
|
175
|
+
|
176
|
+
nbt = Nbt::Nbt.read(bigtest_nbt)
|
177
|
+
puts nbt.to_formatted_s
|
178
|
+
end
|
data/lib/bindata.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# BinData -- Binary data manipulator.
|
2
|
+
# Copyright (c) 2007 - 2013 Dion Mendel.
|
3
|
+
|
4
|
+
require 'bindata/version'
|
5
|
+
require 'bindata/array'
|
6
|
+
require 'bindata/bits'
|
7
|
+
require 'bindata/choice'
|
8
|
+
require 'bindata/count_bytes_remaining'
|
9
|
+
require 'bindata/float'
|
10
|
+
require 'bindata/int'
|
11
|
+
require 'bindata/primitive'
|
12
|
+
require 'bindata/record'
|
13
|
+
require 'bindata/rest'
|
14
|
+
require 'bindata/skip'
|
15
|
+
require 'bindata/string'
|
16
|
+
require 'bindata/stringz'
|
17
|
+
require 'bindata/struct'
|
18
|
+
require 'bindata/trace'
|
19
|
+
require 'bindata/alignment'
|
20
|
+
require 'bindata/deprecated'
|
21
|
+
|
22
|
+
# = BinData
|
23
|
+
#
|
24
|
+
# A declarative way to read and write structured binary data.
|
25
|
+
#
|
26
|
+
# A full reference manual is available online at
|
27
|
+
# http://bindata.rubyforge.org/manual.html
|
28
|
+
#
|
29
|
+
# == License
|
30
|
+
#
|
31
|
+
# BinData is released under the same license as Ruby.
|
32
|
+
#
|
33
|
+
# Copyright (c) 2007 - 2013 Dion Mendel.
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'bindata/base_primitive'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# Resets the stream alignment to the next byte. This is
|
5
|
+
# only useful when using bit-based primitives.
|
6
|
+
#
|
7
|
+
# class MyRec < BinData::Record
|
8
|
+
# bit4 :a
|
9
|
+
# resume_byte_alignment
|
10
|
+
# bit4 :b
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# MyRec.read("\x12\x34") #=> {"a" => 1, "b" => 3}
|
14
|
+
#
|
15
|
+
class ResumeByteAlignment < BinData::Base
|
16
|
+
def clear; end
|
17
|
+
def clear?; true; end
|
18
|
+
def assign(val); end
|
19
|
+
def snapshot; nil; end
|
20
|
+
def do_num_bytes; 0; end
|
21
|
+
|
22
|
+
def do_read(io)
|
23
|
+
io.reset_read_bits
|
24
|
+
end
|
25
|
+
|
26
|
+
def do_write(io)
|
27
|
+
io.flushbits
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# A monkey patch to force byte-aligned primitives to
|
32
|
+
# become bit-aligned. This allows them to be used at
|
33
|
+
# non byte based boundaries.
|
34
|
+
#
|
35
|
+
# class BitString < BinData::String
|
36
|
+
# bit_aligned
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# class MyRecord < BinData::Record
|
40
|
+
# bit4 :preamble
|
41
|
+
# bit_string :str, :length => 2
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
module BitAligned
|
45
|
+
class BitAlignedIO
|
46
|
+
def initialize(io)
|
47
|
+
@io = io
|
48
|
+
end
|
49
|
+
def readbytes(n)
|
50
|
+
n.times.inject("") do |bytes, i|
|
51
|
+
bytes << @io.readbits(8, :big).chr
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def method_missing(sym, *args, &block)
|
55
|
+
@io.send(sym, *args, &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def read_and_return_value(io)
|
60
|
+
super(BitAlignedIO.new(io))
|
61
|
+
end
|
62
|
+
|
63
|
+
def do_num_bytes
|
64
|
+
super.to_f
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_write(io)
|
68
|
+
value_to_binary_string(_value).each_byte { |v| io.writebits(v, 8, :big) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class BasePrimitive < BinData::Base
|
73
|
+
def self.bit_aligned
|
74
|
+
include BitAligned
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Primitive < BinData::BasePrimitive
|
79
|
+
def self.bit_aligned
|
80
|
+
fail "'bit_aligned' is not needed for BinData::Primitives"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
require 'bindata/dsl'
|
3
|
+
|
4
|
+
module BinData
|
5
|
+
# An Array is a list of data objects of the same type.
|
6
|
+
#
|
7
|
+
# require 'bindata'
|
8
|
+
#
|
9
|
+
# data = "\x03\x04\x05\x06\x07\x08\x09"
|
10
|
+
#
|
11
|
+
# obj = BinData::Array.new(:type => :int8, :initial_length => 6)
|
12
|
+
# obj.read(data) #=> [3, 4, 5, 6, 7, 8]
|
13
|
+
#
|
14
|
+
# obj = BinData::Array.new(:type => :int8,
|
15
|
+
# :read_until => lambda { index == 1 })
|
16
|
+
# obj.read(data) #=> [3, 4]
|
17
|
+
#
|
18
|
+
# obj = BinData::Array.new(:type => :int8,
|
19
|
+
# :read_until => lambda { element >= 6 })
|
20
|
+
# obj.read(data) #=> [3, 4, 5, 6]
|
21
|
+
#
|
22
|
+
# obj = BinData::Array.new(:type => :int8,
|
23
|
+
# :read_until => lambda { array[index] + array[index - 1] == 13 })
|
24
|
+
# obj.read(data) #=> [3, 4, 5, 6, 7]
|
25
|
+
#
|
26
|
+
# obj = BinData::Array.new(:type => :int8, :read_until => :eof)
|
27
|
+
# obj.read(data) #=> [3, 4, 5, 6, 7, 8, 9]
|
28
|
+
#
|
29
|
+
# == Parameters
|
30
|
+
#
|
31
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
32
|
+
# an object. These params are:
|
33
|
+
#
|
34
|
+
# <tt>:type</tt>:: The symbol representing the data type of the
|
35
|
+
# array elements. If the type is to have params
|
36
|
+
# passed to it, then it should be provided as
|
37
|
+
# <tt>[type_symbol, hash_params]</tt>.
|
38
|
+
# <tt>:initial_length</tt>:: The initial length of the array.
|
39
|
+
# <tt>:read_until</tt>:: While reading, elements are read until this
|
40
|
+
# condition is true. This is typically used to
|
41
|
+
# read an array until a sentinel value is found.
|
42
|
+
# The variables +index+, +element+ and +array+
|
43
|
+
# are made available to any lambda assigned to
|
44
|
+
# this parameter. If the value of this parameter
|
45
|
+
# is the symbol :eof, then the array will read
|
46
|
+
# as much data from the stream as possible.
|
47
|
+
#
|
48
|
+
# Each data object in an array has the variable +index+ made available
|
49
|
+
# to any lambda evaluated as a parameter of that data object.
|
50
|
+
class Array < BinData::Base
|
51
|
+
include DSLMixin
|
52
|
+
include Enumerable
|
53
|
+
|
54
|
+
dsl_parser :array
|
55
|
+
|
56
|
+
mandatory_parameter :type
|
57
|
+
optional_parameters :initial_length, :read_until
|
58
|
+
mutually_exclusive_parameters :initial_length, :read_until
|
59
|
+
|
60
|
+
class << self
|
61
|
+
|
62
|
+
def sanitize_parameters!(params) #:nodoc:
|
63
|
+
unless params.has_parameter?(:initial_length) or
|
64
|
+
params.has_parameter?(:read_until)
|
65
|
+
# ensure one of :initial_length and :read_until exists
|
66
|
+
params[:initial_length] = 0
|
67
|
+
end
|
68
|
+
|
69
|
+
params.warn_replacement_parameter(:read_length, :initial_length)
|
70
|
+
|
71
|
+
params.merge!(dsl_params)
|
72
|
+
|
73
|
+
if params.needs_sanitizing?(:type)
|
74
|
+
el_type, el_params = params[:type]
|
75
|
+
params[:type] = params.create_sanitized_object_prototype(el_type, el_params)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize_shared_instance
|
81
|
+
@element_prototype = get_parameter(:type)
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize_instance
|
85
|
+
@element_list = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def clear?
|
89
|
+
@element_list.nil? or elements.all? { |el| el.clear? }
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear
|
93
|
+
initialize_instance
|
94
|
+
end
|
95
|
+
|
96
|
+
def assign(array)
|
97
|
+
raise ArgumentError, "can't set a nil value for #{debug_name}" if array.nil?
|
98
|
+
|
99
|
+
@element_list = to_storage_formats(array.to_ary)
|
100
|
+
end
|
101
|
+
|
102
|
+
def snapshot
|
103
|
+
elements.collect { |el| el.snapshot }
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_index(obj)
|
107
|
+
elements.index(obj)
|
108
|
+
end
|
109
|
+
alias_method :index, :find_index
|
110
|
+
|
111
|
+
# Returns the first index of +obj+ in self.
|
112
|
+
#
|
113
|
+
# Uses equal? for the comparator.
|
114
|
+
def find_index_of(obj)
|
115
|
+
elements.index { |el| el.equal?(obj) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def push(*args)
|
119
|
+
insert(-1, *args)
|
120
|
+
self
|
121
|
+
end
|
122
|
+
alias_method :<<, :push
|
123
|
+
|
124
|
+
def unshift(*args)
|
125
|
+
insert(0, *args)
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def concat(array)
|
130
|
+
insert(-1, *array.to_ary)
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
def insert(index, *objs)
|
135
|
+
extend_array(index - 1)
|
136
|
+
elements.insert(index, *to_storage_formats(objs))
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the element at +index+.
|
141
|
+
def [](arg1, arg2 = nil)
|
142
|
+
if arg1.respond_to?(:to_int) and arg2.nil?
|
143
|
+
slice_index(arg1.to_int)
|
144
|
+
elsif arg1.respond_to?(:to_int) and arg2.respond_to?(:to_int)
|
145
|
+
slice_start_length(arg1.to_int, arg2.to_int)
|
146
|
+
elsif arg1.is_a?(Range) and arg2.nil?
|
147
|
+
slice_range(arg1)
|
148
|
+
else
|
149
|
+
raise TypeError, "can't convert #{arg1} into Integer" unless arg1.respond_to?(:to_int)
|
150
|
+
raise TypeError, "can't convert #{arg2} into Integer" unless arg2.respond_to?(:to_int)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
alias_method :slice, :[]
|
154
|
+
|
155
|
+
def slice_index(index)
|
156
|
+
extend_array(index)
|
157
|
+
at(index)
|
158
|
+
end
|
159
|
+
|
160
|
+
def slice_start_length(start, length)
|
161
|
+
elements[start, length]
|
162
|
+
end
|
163
|
+
|
164
|
+
def slice_range(range)
|
165
|
+
elements[range]
|
166
|
+
end
|
167
|
+
private :slice_index, :slice_start_length, :slice_range
|
168
|
+
|
169
|
+
# Returns the element at +index+. Unlike +slice+, if +index+ is out
|
170
|
+
# of range the array will not be automatically extended.
|
171
|
+
def at(index)
|
172
|
+
elements[index]
|
173
|
+
end
|
174
|
+
|
175
|
+
# Sets the element at +index+.
|
176
|
+
def []=(index, value)
|
177
|
+
extend_array(index)
|
178
|
+
elements[index].assign(value)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns the first element, or the first +n+ elements, of the array.
|
182
|
+
# If the array is empty, the first form returns nil, and the second
|
183
|
+
# form returns an empty array.
|
184
|
+
def first(n = nil)
|
185
|
+
if n.nil? and empty?
|
186
|
+
# explicitly return nil as arrays grow automatically
|
187
|
+
nil
|
188
|
+
elsif n.nil?
|
189
|
+
self[0]
|
190
|
+
else
|
191
|
+
self[0, n]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns the last element, or the last +n+ elements, of the array.
|
196
|
+
# If the array is empty, the first form returns nil, and the second
|
197
|
+
# form returns an empty array.
|
198
|
+
def last(n = nil)
|
199
|
+
if n.nil?
|
200
|
+
self[-1]
|
201
|
+
else
|
202
|
+
n = length if n > length
|
203
|
+
self[-n, n]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def length
|
208
|
+
elements.length
|
209
|
+
end
|
210
|
+
alias_method :size, :length
|
211
|
+
|
212
|
+
def empty?
|
213
|
+
length.zero?
|
214
|
+
end
|
215
|
+
|
216
|
+
# Allow this object to be used in array context.
|
217
|
+
def to_ary
|
218
|
+
collect { |el| el }
|
219
|
+
end
|
220
|
+
|
221
|
+
def each
|
222
|
+
elements.each { |el| yield el }
|
223
|
+
end
|
224
|
+
|
225
|
+
def debug_name_of(child) #:nodoc:
|
226
|
+
index = find_index_of(child)
|
227
|
+
"#{debug_name}[#{index}]"
|
228
|
+
end
|
229
|
+
|
230
|
+
def offset_of(child) #:nodoc:
|
231
|
+
index = find_index_of(child)
|
232
|
+
sum = sum_num_bytes_below_index(index)
|
233
|
+
|
234
|
+
child.do_num_bytes.is_a?(Integer) ? sum.ceil : sum.floor
|
235
|
+
end
|
236
|
+
|
237
|
+
def do_read(io) #:nodoc:
|
238
|
+
if has_parameter?(:initial_length)
|
239
|
+
elements.each { |el| el.do_read(io) }
|
240
|
+
elsif has_parameter?(:read_until)
|
241
|
+
read_until(io)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def do_write(io) #:nodoc:
|
246
|
+
elements.each { |el| el.do_write(io) }
|
247
|
+
end
|
248
|
+
|
249
|
+
def do_num_bytes #:nodoc:
|
250
|
+
sum_num_bytes_for_all_elements
|
251
|
+
end
|
252
|
+
|
253
|
+
#---------------
|
254
|
+
private
|
255
|
+
|
256
|
+
def extend_array(max_index)
|
257
|
+
max_length = max_index + 1
|
258
|
+
while elements.length < max_length
|
259
|
+
append_new_element
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def to_storage_formats(els)
|
264
|
+
els.collect { |el| new_element(el) }
|
265
|
+
end
|
266
|
+
|
267
|
+
def read_until(io)
|
268
|
+
if get_parameter(:read_until) == :eof
|
269
|
+
read_until_eof(io)
|
270
|
+
else
|
271
|
+
read_until_condition(io)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def read_until_eof(io)
|
276
|
+
loop do
|
277
|
+
element = append_new_element
|
278
|
+
begin
|
279
|
+
element.do_read(io)
|
280
|
+
rescue EOFError, IOError
|
281
|
+
elements.pop
|
282
|
+
break
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def read_until_condition(io)
|
288
|
+
loop do
|
289
|
+
element = append_new_element
|
290
|
+
element.do_read(io)
|
291
|
+
variables = { :index => self.length - 1, :element => self.last,
|
292
|
+
:array => self }
|
293
|
+
break if eval_parameter(:read_until, variables)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def elements
|
298
|
+
if @element_list.nil?
|
299
|
+
@element_list = []
|
300
|
+
if has_parameter?(:initial_length)
|
301
|
+
eval_parameter(:initial_length).times do
|
302
|
+
@element_list << new_element
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
@element_list
|
307
|
+
end
|
308
|
+
|
309
|
+
def append_new_element
|
310
|
+
element = new_element
|
311
|
+
elements << element
|
312
|
+
element
|
313
|
+
end
|
314
|
+
|
315
|
+
def new_element(value = nil)
|
316
|
+
@element_prototype.instantiate(value, self)
|
317
|
+
end
|
318
|
+
|
319
|
+
def sum_num_bytes_for_all_elements
|
320
|
+
sum_num_bytes_below_index(length)
|
321
|
+
end
|
322
|
+
|
323
|
+
def sum_num_bytes_below_index(index)
|
324
|
+
(0...index).inject(0) do |sum, i|
|
325
|
+
nbytes = elements[i].do_num_bytes
|
326
|
+
|
327
|
+
if nbytes.is_a?(Integer)
|
328
|
+
sum.ceil + nbytes
|
329
|
+
else
|
330
|
+
sum + nbytes
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|