snow-data 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 425085a25eb678abb07c07ba25d1a52142d86822
4
- data.tar.gz: b04d708b4f18e405354bd56f7175e4ddaec4291e
3
+ metadata.gz: de5502dc1786af224a1344f0221110e87bae22a0
4
+ data.tar.gz: 207bffe6bbe3d1bf6f6ff3bef67615b12ef3767d
5
5
  SHA512:
6
- metadata.gz: f03aa50d5ad415403388c91d3560b0c3ca188ef8309797c3f3e6f342380c4880b37e224dd025870fa420ec8819b8d7d8a68e0e7aea13d63ab16b150b1228a846
7
- data.tar.gz: 17975205c4c120f24ebd00ebe0feb6d47699847a063564a51e42f6a9b66a50d95a4b1ceb6414081bfe79620f6f9dd7ef235a37745db6616a84f738cb0caa1629
6
+ metadata.gz: c38b07ce23e8fc20c451c5d529d535e9bbe826a5e41f149d5903c29a53ac7d01024d0becda54c7e75e4c24e07f1b216fe4a87a45098394b121eb3de309bd860f
7
+ data.tar.gz: a71c448abd01932b31ec66d5bb197469b6b6521404fa270f20a45ae150eb51d798011e1fe56daea4d1421d684cf145c333582ecd6aa1d1db523f1bca01a5b5b6
data/ext/extconf.rb CHANGED
@@ -19,6 +19,7 @@ option_mappings = {
19
19
  '-Ws' => OptKVPair[:warn_implicit_size, true],
20
20
  '--warn-no-bytesize' => OptKVPair[:warn_no_bytesize, true],
21
21
  '-Wbs' => OptKVPair[:warn_implicit_size, true],
22
+ '--allow-alloca' => OptKVPair[:allow_alloca, true],
22
23
  '--debug-memory-copy' => OptKVPair[:debug_memory_copy, true],
23
24
  '--debug-allocations' => OptKVPair[:debug_allocations, true]
24
25
  }
@@ -27,6 +28,7 @@ options = {
27
28
  :build_debug => false,
28
29
  :warn_implicit_size => false,
29
30
  :warn_no_bytesize => false,
31
+ :allow_alloca => false,
30
32
  :debug_memory_copy => false,
31
33
  :debug_allocations => false
32
34
  }
@@ -50,6 +52,7 @@ else
50
52
  $stdout.puts "Building extension in release mode"
51
53
  end
52
54
 
55
+ $CFLAGS += ' -DSD_ALLOW_ALLOCA' if options[:allow_alloca]
53
56
  $CFLAGS += ' -DSD_SD_WARN_ON_IMPLICIT_COPY_SIZE' if options[:warn_implicit_size]
54
57
  $CFLAGS += ' -DSD_WARN_ON_NO_BYTESIZE_METHOD' if options[:warn_no_bytesize]
55
58
  $CFLAGS += ' -DSD_VERBOSE_COPY_LOG' if options[:debug_memory_copy]
@@ -8,6 +8,11 @@
8
8
  #include <stdint.h>
9
9
  #include <stdio.h>
10
10
 
11
+ typedef enum e_sd_free_memory_flag {
12
+ SD_DO_NOT_FREE_MEMORY = 0,
13
+ SD_FREE_MEMORY = 1
14
+ } sd_free_memory_flag_t;
15
+
11
16
  static ID kSD_IVAR_BYTESIZE;
12
17
  static ID kSD_IVAR_ALIGNMENT;
13
18
  static ID kSD_ID_BYTESIZE;
@@ -175,33 +180,54 @@ static void *align_ptr(void *ptr, size_t alignment)
175
180
  /*
176
181
  Allocated a block of memory of at least size bytes aligned to the given byte
177
182
  alignment.
183
+
184
+ Raises a NoMemoryError if it's not possible to allocate memory.
178
185
  */
179
186
  static void *com_malloc(size_t size, size_t alignment)
180
187
  {
181
188
  const size_t aligned_size = align_size(size + sizeof(void *), alignment);
182
189
  void *const ptr = xcalloc(aligned_size, 1);
183
- void **const aligned_ptr = align_ptr((uint8_t *)ptr + sizeof(void *), alignment);
190
+ void **aligned_ptr;
191
+
192
+ if (!ptr) {
193
+ rb_raise(rb_eNoMemError,
194
+ "Failed to allocate %zu (req: %zu) bytes via malloc",
195
+ aligned_size, size);
196
+ return NULL;
197
+ }
198
+
199
+ aligned_ptr = align_ptr((uint8_t *)ptr + sizeof(void *), alignment);
184
200
  aligned_ptr[-1] = ptr;
201
+
185
202
  #ifdef SD_VERBOSE_MALLOC_LOG
186
203
  fprintf(stderr, "Allocated block %p with aligned size %zu (requested: %zu"
187
204
  " aligned to %zu bytes), returning aligned pointer %p with usable size %td\n",
188
205
  ptr, aligned_size, size, alignment, aligned_ptr,
189
206
  (uint8_t *)(ptr + aligned_size) - (uint8_t *)aligned_ptr);
190
207
  #endif
208
+
191
209
  return aligned_ptr;
192
210
  }
193
211
 
194
212
  /*
195
213
  Frees memory previously allocated by com_malloc. This _does not work_ if it
196
214
  was allocated by any other means.
215
+
216
+ Raises a RuntimeError if aligned_ptr is NULL.
197
217
  */
198
218
  static void com_free(void *aligned_ptr)
199
219
  {
220
+ if (!aligned_ptr) {
221
+ rb_raise(rb_eRuntimeError, "Attempt to call free on NULL");
222
+ return;
223
+ }
224
+
200
225
  #ifdef SD_VERBOSE_MALLOC_LOG
201
226
  fprintf(stderr, "Deallocating aligned pointer %p with underlying pointer %p\n",
202
227
  aligned_ptr,
203
228
  ((void **)aligned_ptr)[-1]);
204
229
  #endif
230
+
205
231
  xfree(((void **)aligned_ptr)[-1]);
206
232
  }
207
233
 
@@ -1286,9 +1312,44 @@ static VALUE sd_set_string(int argc, VALUE *argv, VALUE self)
1286
1312
  return sd_set_string_nullterm(self, sd_offset, sd_value, !!RTEST(sd_null_terminated));
1287
1313
  }
1288
1314
 
1315
+ /*
1316
+ Frees memory associated with self regardless of whether the object is frozen.
1317
+ */
1318
+ static void sd_memory_force_free(VALUE self)
1319
+ {
1320
+ struct RData *data = RDATA(self);
1321
+
1322
+ if (data->data && data->dfree) {
1323
+ data->dfree(data->data);
1324
+ data->dfree = 0;
1325
+ } else if (!data->data) {
1326
+ rb_raise(rb_eRuntimeError,
1327
+ "Double-free on %s",
1328
+ rb_obj_classname(self));
1329
+ }
1330
+
1331
+ data->data = 0;
1332
+ rb_ivar_set(self, kSD_IVAR_BYTESIZE, INT2FIX(0));
1333
+ }
1334
+
1335
+ /*
1336
+ Returns a Data object wrapped with the given klass and the appropriate size
1337
+ and alignment instance variables.
1338
+ */
1339
+ static VALUE sd_wrap_memory(VALUE klass, void *data, size_t size, size_t alignment, sd_free_memory_flag_t should_free)
1340
+ {
1341
+ VALUE memory = Data_Wrap_Struct(klass, 0, (should_free ? com_free : 0), data);
1342
+ rb_ivar_set(memory, kSD_IVAR_BYTESIZE, SIZET2NUM(size));
1343
+ rb_ivar_set(memory, kSD_IVAR_ALIGNMENT, SIZET2NUM(alignment));
1344
+ rb_obj_call_init(memory, 0, 0);
1345
+ return memory;
1346
+ }
1347
+
1289
1348
  /*
1290
1349
  call-seq:
1291
1350
  new(address, size, alignment = SIZEOF_VOID_POINTER) => Memory
1351
+ wrap(address, size, alignment = SIZEOF_VOID_POINTER) => Memory
1352
+ __wrap__(address, size, alignment = SIZEOF_VOID_POINTER) => Memory
1292
1353
 
1293
1354
  Creates a new Memory object that wraps an existing pointer. Alignment is
1294
1355
  optional and defaults to the size of a pointer (Memory::SIZEOF_VOID_POINTER).
@@ -1310,7 +1371,7 @@ static VALUE sd_set_string(int argc, VALUE *argv, VALUE self)
1310
1371
  an ArgumentError to provide a NULL (zero) address.
1311
1372
 
1312
1373
  If a subclass overrides ::new, it is also aliased as ::wrap and ::__wrap__.
1313
- Subclasses may override ::wrap but must not override ::__wrap__.
1374
+ Subclasses may override ::wrap or ::new but must never override ::__wrap__.
1314
1375
  */
1315
1376
  static VALUE sd_memory_new(int argc, VALUE *argv, VALUE self)
1316
1377
  {
@@ -1346,11 +1407,7 @@ static VALUE sd_memory_new(int argc, VALUE *argv, VALUE self)
1346
1407
  alignment = NUM2SIZET(sd_alignment);
1347
1408
  }
1348
1409
 
1349
- memory = Data_Wrap_Struct(self, 0, 0, address);
1350
- rb_ivar_set(memory, kSD_IVAR_BYTESIZE, SIZET2NUM(size));
1351
- rb_ivar_set(memory, kSD_IVAR_ALIGNMENT, SIZET2NUM(alignment));
1352
- rb_obj_call_init(memory, 0, 0);
1353
- rb_obj_taint(memory);
1410
+ memory = sd_wrap_memory(self, address, size, alignment, SD_DO_NOT_FREE_MEMORY);
1354
1411
 
1355
1412
  return memory;
1356
1413
  }
@@ -1358,11 +1415,16 @@ static VALUE sd_memory_new(int argc, VALUE *argv, VALUE self)
1358
1415
  /*
1359
1416
  call-seq:
1360
1417
  malloc(size, alignment = nil) => Memory
1418
+ __malloc__(size, alignment = nil) => Memory
1361
1419
 
1362
1420
  Allocates a new block with the given size and alignment and returns it. If
1363
1421
  no alignment is specified, it defaults to Snow::Memory::SIZEOF_VOID_POINTER.
1364
1422
 
1365
1423
  Raises a RangeError if either size is zero or alignment is not a power of two.
1424
+
1425
+ If a subclass overrides ::malloc, which is a bad idea and should not be done,
1426
+ then it is aliased as ::__malloc__ as well. Subclasses must never override
1427
+ ::__malloc__.
1366
1428
  */
1367
1429
  static VALUE sd_memory_malloc(int argc, VALUE *argv, VALUE self)
1368
1430
  {
@@ -1388,15 +1450,82 @@ static VALUE sd_memory_malloc(int argc, VALUE *argv, VALUE self)
1388
1450
 
1389
1451
  /* Allocate block */
1390
1452
  data = com_malloc(size, alignment);
1391
- memory = Data_Wrap_Struct(self, 0, com_free, data);
1453
+ memory = sd_wrap_memory(self, data, size, alignment, SD_FREE_MEMORY);
1392
1454
 
1393
- rb_ivar_set(memory, kSD_IVAR_BYTESIZE, SIZET2NUM(size));
1394
- rb_ivar_set(memory, kSD_IVAR_ALIGNMENT, SIZET2NUM(alignment));
1395
- rb_obj_call_init(memory, 0, 0);
1396
- rb_obj_taint(memory);
1397
1455
  return memory;
1398
1456
  }
1399
1457
 
1458
+ #ifdef SD_ALLOW_ALLOCA
1459
+ /*
1460
+ call-seq:
1461
+ alloca(size) { |memory| ... } => result of block
1462
+ __alloca__(size) { |memory| ... } => result of block
1463
+
1464
+ Allocates size bytes on the stack and yields it to a block before for use,
1465
+ then returns the result of the block. Outside of the block, any use of the
1466
+ yielded memory's address is considered undefined behavior and may lead to
1467
+ crashes or worse. The yielded memory is only valid for the duration of the
1468
+ block. If you want to preserve the memory after use, either duplicate the
1469
+ memory block or realloc! the memory, both of which will copy it from the stack
1470
+ to the heap.
1471
+
1472
+ The alignment of the yielded memory is always the size of a pointer.
1473
+
1474
+ Because of how downright evil alloca is in some situations, it is disabled by
1475
+ default. You must explicitly install snow-data with the --allow-alloca flag to
1476
+ use this function. If you're not certain whether alloca is available for a
1477
+ particular installation, then you should check if the Memory class responds to
1478
+ it first.
1479
+
1480
+ if Snow::Memory.respond_to?(:__alloca__)
1481
+ Snow::Memory.__alloca__(64) { |mem|
1482
+ # ...
1483
+ }
1484
+ end
1485
+
1486
+ Subclasses may override alloca to provide a predefined size argument, but
1487
+ subclasses must never override __alloca__. It is considered bad form to
1488
+ implement __alloca__ using heap allocation for installations that don't
1489
+ provide it.
1490
+ */
1491
+ static VALUE sd_memory_alloca(VALUE self, VALUE sd_size)
1492
+ {
1493
+ VALUE result = Qnil;
1494
+ void *stack_memory = NULL;
1495
+ struct RData *block_data;
1496
+ size_t size = NUM2SIZET(sd_size);
1497
+ VALUE block;
1498
+
1499
+ rb_need_block();
1500
+
1501
+ if (size == 0) {
1502
+ rb_raise(rb_eRangeError, "Size of block must be 1 or more -- zero-byte"
1503
+ " blocks are not permitted.");
1504
+ }
1505
+
1506
+ stack_memory = alloca(size);
1507
+ if (stack_memory == NULL) {
1508
+ rb_raise(rb_eNoMemError,
1509
+ "Failed to allocated %zu bytes via alloca",
1510
+ size);
1511
+ }
1512
+
1513
+ block = sd_wrap_memory(self, stack_memory, size, SIZEOF_VOIDP, SD_DO_NOT_FREE_MEMORY);
1514
+ block_data = RDATA(block);
1515
+ result = rb_yield(block);
1516
+
1517
+ /*
1518
+ If the block hasn't been realloc!'d or freed, free it now if it hasn't been
1519
+ frozen for some reason.
1520
+ */
1521
+ if (block_data->data == stack_memory && !block_data->dfree && !OBJ_FROZEN(block)) {
1522
+ sd_memory_force_free(block);
1523
+ }
1524
+
1525
+ return result;
1526
+ }
1527
+ #endif
1528
+
1400
1529
  /*
1401
1530
  call-seq:
1402
1531
  realloc!(size, alignment = nil) => self
@@ -1488,22 +1617,8 @@ static VALUE sd_memory_realloc(int argc, VALUE *argv, VALUE self)
1488
1617
  */
1489
1618
  static VALUE sd_memory_free(VALUE self)
1490
1619
  {
1491
- struct RData *data = RDATA(self);
1492
-
1493
1620
  rb_check_frozen(self);
1494
-
1495
- if (data->data && data->dfree) {
1496
- data->dfree(data->data);
1497
- data->dfree = 0;
1498
- } else if (!data->data) {
1499
- rb_raise(rb_eRuntimeError,
1500
- "Double-free on %s",
1501
- rb_obj_classname(self));
1502
- }
1503
-
1504
- data->data = 0;
1505
- rb_ivar_set(self, kSD_IVAR_BYTESIZE, INT2FIX(0));
1506
-
1621
+ sd_memory_force_free(self);
1507
1622
  return self;
1508
1623
  }
1509
1624
 
@@ -1804,8 +1919,11 @@ void Init_snowdata_bindings(void)
1804
1919
  rb_const_set(sd_memory_klass, rb_intern("SIZEOF_UINTPTR_T"), SIZET2NUM(SIZEOF_UINTPTR_T));
1805
1920
  rb_const_set(sd_memory_klass, rb_intern("SIZEOF_VOID_POINTER"), SIZET2NUM(sizeof(void *)));
1806
1921
 
1807
- rb_define_singleton_method(sd_memory_klass, "new", sd_memory_new, -1);
1808
- rb_define_singleton_method(sd_memory_klass, "malloc", sd_memory_malloc, -1);
1922
+ rb_define_singleton_method(sd_memory_klass, "__wrap__", sd_memory_new, -1);
1923
+ rb_define_singleton_method(sd_memory_klass, "__malloc__", sd_memory_malloc, -1);
1924
+ #ifdef SD_ALLOW_ALLOCA
1925
+ rb_define_singleton_method(sd_memory_klass, "__alloca__", sd_memory_alloca, 1);
1926
+ #endif
1809
1927
  rb_define_singleton_method(sd_memory_klass, "align_size", sd_align_size, -1);
1810
1928
  rb_define_method(sd_memory_klass, "realloc!", sd_memory_realloc, -1);
1811
1929
  rb_define_method(sd_memory_klass, "copy!", sd_memory_copy, -1);
@@ -4,6 +4,8 @@
4
4
 
5
5
  require 'snow-data/snowdata_bindings'
6
6
  require 'snow-data/memory'
7
+ require 'snow-data/c_struct/struct_base'
8
+ require 'snow-data/c_struct/array_base'
7
9
 
8
10
  module Snow
9
11
 
@@ -17,186 +19,42 @@ class CStruct
17
19
  StructMemberInfo = Struct.new(:name, :type, :size, :length, :alignment, :offset)
18
20
 
19
21
 
20
-
21
22
  #
22
- # Struct base class. Not to be used directly, as it does not provide all the
23
- # constants and methods necessary for a struct.
23
+ # Whether long inspect strings are enabled. See both ::long_inspect= and
24
+ # ::long_inspect for accessors.
24
25
  #
25
- class StructBase < ::Snow::Memory
26
-
27
- @@long_inspect = false
28
-
29
- #
30
- # Whether long inspect strings are enabled. By default, they are disabled.
31
- #
32
- # Long inspect strings can be useful for debugging, sepcially if you want to
33
- # see the value, length, and alignment of every struct member in inspect
34
- # strings. Otherwise, you can safely leave this disabled.
35
- #
36
- def self.long_inspect=(enabled)
37
- @@long_inspect = !!enabled
38
- end
39
-
40
-
41
- def self.long_inspect
42
- @@long_inspect
43
- end
44
-
45
-
46
- #
47
- # Returns the offset of a member.
48
- #
49
- def self.offset_of(member)
50
- self.MEMBERS_HASH[member].offset
51
- end
52
-
53
-
54
- #
55
- # Returns the type name of a member.
56
- #
57
- def self.type_of(member)
58
- self.MEMBERS_HASH[member].type
59
- end
60
-
61
-
62
- #
63
- # Returns the size in bytes of a member.
64
- #
65
- def self.bytesize_of(member)
66
- self.MEMBERS_HASH[member].size
67
- end
68
-
69
-
70
- #
71
- # Returns the alignment of a member.
72
- #
73
- def self.alignment_of(member)
74
- self.MEMBERS_HASH[member].alignment
75
- end
76
-
77
-
78
- #
79
- # Returns the length of a member.
80
- #
81
- def self.length_of(member)
82
- self.MEMBERS_HASH[member].length
83
- end
84
-
85
-
86
-
87
- #
88
- # Returns the address of a member. This address is only valid for the
89
- # receiver.
90
- #
91
- def address_of(member)
92
- self.address + self.class.offset_of(member)
93
- end
94
-
95
-
96
- #
97
- # Returns the offset of a member.
98
- #
99
- def offset_of(member)
100
- self.class.offset_of(member)
101
- end
102
-
103
-
104
- #
105
- # Returns the size in bytes of a member.
106
- #
107
- def bytesize_of(member)
108
- self.class.bytesize_of(member)
109
- end
110
-
111
-
112
- #
113
- # Returns the alignment of a member.
114
- #
115
- def alignment_of(member)
116
- self.class.alignment_of(member)
117
- end
118
-
119
-
120
- #
121
- # Returns the length of a member.
122
- #
123
- def length_of(member)
124
- self.class.length_of(member)
125
- end
126
-
127
-
128
- #
129
- # Returns the type name of a member.
130
- #
131
- def type_of(member)
132
- self.class.type_of(member)
133
- end
134
-
135
-
136
- #
137
- # Returns a hash of all of the struct's member names to their values.
138
- #
139
- def to_h
140
- self.class::MEMBERS.inject({}) do |hash, member|
141
- length = member.length
142
- name = member.name
143
-
144
- hash[name] = if length > 1
145
- (0 ... length).map { |index| __send__(name, index) }
146
- else
147
- __send__(name)
148
- end
149
-
150
- hash
151
- end
152
- end
26
+ @@long_inspect = false
153
27
 
154
28
 
155
- #
156
- # Returns a string describing the struct, including its classname, object
157
- # ID, address, size, and alignment. In addition, if long_inspect is enabled,
158
- # then it will also include the values of the struct's members.
159
- #
160
- def inspect
161
- id_text = __id__.to_s(16).rjust(14, ?0)
162
- addr_text = self.address.to_s(16).rjust(14, ?0)
163
-
164
- member_text = if ! null? && self.class.long_inspect
165
- # Get member text
166
- all_members_text = (self.class::MEMBERS.map do |member|
167
- name = member.name
168
- type = member.type
169
- align = member.alignment
170
- length = member.length
171
- length_decl = length > 1 ? "[#{length}]" : ''
172
-
173
- values_text = if length > 1
174
- single_member_text = (0 ... length).map { |index|
175
- "[#{ index }]=#{ self.__send__(member.name, index).inspect }"
176
- }.join(', ')
177
-
178
- "{ #{ single_member_text } }"
179
- else
180
- self.__send__(member.name).inspect
181
- end
182
-
183
- "#{ name }:#{ type }#{ length_decl }:#{ align }=#{ values_text }"
184
-
185
- end).join('; ')
186
-
187
- " #{all_members_text}"
188
- else # member_text = if ...
189
- # Skip members
190
- ''
191
- end # member_text = if ...
192
-
193
- "<#{ self.class }:0x#{ id_text } *0x#{ addr_text }:#{ self.bytesize }:#{ self.alignment }#{member_text}>"
194
- end
29
+ #
30
+ # call-seq:
31
+ # long_inspect = boolean => boolean
32
+ #
33
+ # Sets whether long inspect strings are enabled. By default, they are disabled.
34
+ #
35
+ # Long inspect strings can be useful for debugging, sepcially if you want to
36
+ # see the value, length, and alignment of every struct member in inspect
37
+ # strings. Otherwise, you can safely leave this disabled.
38
+ #
39
+ def self.long_inspect=(enabled)
40
+ @@long_inspect = !!enabled
41
+ end
195
42
 
196
43
 
197
- end # class StructBase
44
+ #
45
+ # call-seq:
46
+ # long_inspect => boolean
47
+ #
48
+ # Returns whether long_inspect is enabled. By default, it is disabled.
49
+ #
50
+ def self.long_inspect
51
+ @@long_inspect
52
+ end
198
53
 
199
54
 
55
+ #
56
+ # call-seq:
57
+ # power_of_two?(num) => boolean
200
58
  #
201
59
  # Returns whether num is a power of two and nonzero.
202
60
  #
@@ -205,6 +63,9 @@ class CStruct
205
63
  end
206
64
 
207
65
 
66
+ #
67
+ # call-seq:
68
+ # member_encoding(name, type, length: 1, alignment: nil) => String
208
69
  #
209
70
  # Returns an encoding for a struct member with the given name, type,
210
71
  # length, and alignment. The type must be a string or symbol, not a Class or
@@ -228,6 +89,9 @@ class CStruct
228
89
  #--
229
90
  # Ordinarily I'd write a lexer for this sort of thing, but regex actually
230
91
  # seems to work fine.
92
+ #
93
+ # TODO: At any rate, replace this with a lexer/parser. At least that way it'll
94
+ # be possible to provide validation for encodings.
231
95
  #++
232
96
  ENCODING_REGEX = %r{
233
97
  (?<name> # 0
@@ -247,6 +111,7 @@ class CStruct
247
111
  \s* (?: ; | $ | \n) # terminator
248
112
  }mx
249
113
 
114
+
250
115
  # Alignemnts for default types.
251
116
  ALIGNMENTS = {
252
117
  :char => 1,
@@ -276,6 +141,7 @@ class CStruct
276
141
  :uintptr_t => Memory::SIZEOF_UINTPTR_T
277
142
  }
278
143
 
144
+
279
145
  # Sizes of default types.
280
146
  SIZES = {
281
147
  :char => 1,
@@ -305,6 +171,7 @@ class CStruct
305
171
  :uintptr_t => Memory::SIZEOF_UINTPTR_T
306
172
  }
307
173
 
174
+
308
175
  # Used for getters/setters on Memory objects. Simply maps short type names to
309
176
  # their long-form type names.
310
177
  LONG_NAMES = {
@@ -488,233 +355,140 @@ class CStruct
488
355
  # `fetch(index)` and `store(index, value)` methods, both aliased to `[]` and
489
356
  # `[]=` respectively.
490
357
  #
491
- def self.new(*args)
492
- encoding, klass_name = case args.length
493
- when 1 then args
494
- when 2 then args.reverse
495
- else
496
- raise ArgumentError, "Invalid arguments to CStruct::new"
497
- end
498
-
358
+ def self.new(klass_name = nil, encoding)
499
359
  klass_name = klass_name.intern if klass_name
500
360
 
501
- members = []
502
-
503
- encoding.scan(ENCODING_REGEX) do
504
- |match|
505
- name = match[0].intern
506
- type = match[1].intern
507
- type = LONG_NAMES[type] if LONG_NAMES.include?(type)
508
- length = (match[3] || 1).to_i
509
- align = (match[5] || ALIGNMENTS[type] || 1).to_i
510
- offset = 0
511
-
512
- last_type = members.last
513
- if last_type
514
- offset += Memory.align_size(last_type.offset + last_type.size, align)
515
- end
516
-
517
- members << StructMemberInfo[name, type, SIZES[type] * length, length, align, offset].freeze
518
- end
361
+ members = decode_member_info(encoding)
519
362
 
520
363
  raise "No valid members found in encoding" if members.empty?
521
364
 
522
- alignment = members.map { |member| member.alignment }.max { |lhs, rhs| lhs <=> rhs }
523
- size = members.last.size + members.last.offset
524
- aligned_size = Memory.align_size(size, alignment)
525
-
526
- members.freeze
527
-
528
- klass = Class.new(StructBase) do |struct_klass|
529
- const_set(:ENCODING, String.new(encoding).freeze)
530
- const_set(:MEMBERS, members)
531
- const_set(:SIZE, size)
532
- const_set(:ALIGNED_SIZE, aligned_size)
533
- const_set(:ALIGNMENT, alignment)
534
- const_set(:MEMBERS_HASH, members.reduce({}) { |offs, member| offs[member.name] = member ; offs })
535
-
536
- def self.new(&block)
537
- inst = __malloc__(self::SIZE, self::ALIGNMENT)
538
- yield(inst) if block_given?
539
- inst
540
- end
541
-
542
- self::MEMBERS.each do
543
- |member|
544
-
545
- name = member.name
546
- index_range = (0...member.length)
547
- type_name = member.type
548
- type_size = ::Snow::CStruct::SIZES[member.type]
549
- offset = member.offset
550
- getter = :"get_#{type_name}"
551
- setter = :"set_#{type_name}"
552
- get_name = :"get_#{name}"
553
- set_name = :"set_#{name}"
554
-
555
- define_method(get_name) do |index = 0|
556
- if index === index_range
557
- raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
558
- end
559
- off = offset + index * type_size
560
- __send__(getter, off)
561
- end # get_name
562
-
563
-
564
- define_method(set_name) do |value, index = 0|
565
- if index === index_range
566
- raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
567
- end
568
- off = offset + index * type_size
569
- __send__(setter, off, value)
570
- value
571
- end # set_name
572
-
573
-
574
- alias_method :"#{name}", get_name
575
- alias_method :"#{name}=", set_name
576
-
577
- end # define member get / set methods
578
-
579
-
580
- # Array inner class (not a subclass of StructBase because it has no members itself)
581
- const_set(:Array, Class.new(Memory) do |array_klass|
582
-
583
- const_set(:BASE, struct_klass)
584
-
585
- # The length of the array.
586
- attr_reader :length
587
-
588
-
589
- include Enumerable
590
-
591
-
592
- def self.wrap(address, length_in_elements) # :nodoc:
593
- __wrap__(address, length_in_elements * self::BASE::SIZE)
594
- end
595
-
596
-
597
- def self.new(length) # :nodoc:
598
- length = length.to_i
599
- raise ArgumentError, "Length must be greater than zero" if length < 1
600
- inst = __malloc__(length * self::BASE::SIZE, self::BASE::ALIGNMENT)
601
- inst.instance_variable_set(:@length, length)
602
- inst.instance_variable_set(:@__cache__, nil)
603
- inst
604
- end
605
-
606
-
607
- def resize!(new_length) # :nodoc:
608
- raise ArgumentError, "Length must be greater than zero" if new_length < 1
609
- realloc!(new_length * self.class::BASE::SIZE, self.class::BASE::ALIGNMENT)
610
- @length = new_length
611
- __free_cache__
612
- self
613
- end
614
-
615
-
616
- def each(&block) # :nodoc:
617
- return to_enum(:each) unless block_given?
618
- (0 ... self.length).each { |index| yield fetch(index) }
619
- self
620
- end
365
+ klass = build_struct_type(members)
621
366
 
367
+ if klass_name
368
+ const_set(klass_name, klass)
369
+ add_type(klass_name, klass)
370
+ end
622
371
 
623
- def map(&block) # :nodoc:
624
- return to_enum(:map) unless block_given?
625
- self.dup.map!(&block)
626
- end
372
+ klass
373
+ end
627
374
 
628
375
 
629
- def map!(&block) # :nodoc:
630
- return to_enum(:map!) unless block_given?
631
- (0 ... self.length).each { |index| store(index, yield(fetch(index))) }
632
- self
633
- end
376
+ #
377
+ # Decodes an encoding string and returns an array of StructMemberInfo objects
378
+ # describing the members of a struct for the given encoding. You may then pass
379
+ # this array to build_struct_type to create a new struct class or
380
+ # encode_member_info to get an encoding string for the encoding string you
381
+ # just decoded, as though that were useful to you somehow.
382
+ #
383
+ def self.decode_member_info(encoding)
384
+ total_size = 0
385
+ encoding.scan(ENCODING_REGEX).map do
386
+ |match|
387
+ name = match[0].intern
388
+ type = match[1].intern
389
+ type = LONG_NAMES[type] if LONG_NAMES.include?(type)
390
+ length = (match[3] || 1).to_i
391
+ align = (match[5] || ALIGNMENTS[type] || 1).to_i
392
+ size = SIZES[type] * length
393
+ offset = Memory.align_size(total_size, align)
394
+ total_size = offset + size
395
+
396
+ StructMemberInfo[name, type, size, length, align, offset]
397
+ end
398
+ end
634
399
 
635
400
 
636
- def to_a # :nodoc:
637
- (0 ... self.length).map { |index| fetch(index) }
638
- end
401
+ #
402
+ # Given an array of StructMemberInfo objects, returns a valid encoding string
403
+ # for those objects in the order they're specified in the array. The info
404
+ # objects' offsets are ignored, as these cannot be specified using an encoding
405
+ # string.
406
+ #
407
+ def self.encode_member_info(members)
408
+ members.map { |member|
409
+ "#{member.name}:#{member.type}[#{member.length}]:#{member.alignment}"
410
+ }.join(?;)
411
+ end
639
412
 
640
413
 
641
- def fetch(index) # :nodoc:
642
- raise RuntimeError, "Attempt to access deallocated array" if @length == 0
643
- raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
644
- __build_cache__ if ! @__cache__
645
- @__cache__[index]
646
- end
647
- alias_method :[], :fetch
648
-
649
-
650
- #
651
- # You can use this to assign _any_ Data subclass to an array value, but
652
- # keep in mind that the data assigned MUST -- again, MUST -- be at least
653
- # as large as the array's base struct type in bytes or the assigned
654
- # data object MUST respond to a bytesize message to get its size in
655
- # bytes.
656
- #
657
- def store(index, data) # :nodoc:
658
- raise RuntimeError, "Attempt to access deallocated array" if @length == 0
659
- raise TypeError, "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
660
- raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
661
- @__cache__[index].copy!(data)
662
- data
663
- end
664
- alias_method :[]=, :store
414
+ #
415
+ # call-seq:
416
+ # build_struct_type(members) => Class
417
+ #
418
+ # Builds a struct type for the given array of StructMemberInfo objects. The
419
+ # array of member objects must not be empty.
420
+ #
421
+ def self.build_struct_type(members)
422
+ raise ArgumentError, "Members array must not be empty" if members.empty?
665
423
 
424
+ # Make a copy of the members array so we can store a frozen version of it
425
+ # in the new struct class.
426
+ members = Marshal.load(Marshal.dump(members))
427
+ members.map! { |info| info.freeze }
428
+ members.freeze
666
429
 
430
+ # Get the alignment, size, aligned size, and encoding of the struct.
431
+ alignment = members.map { |member| member.alignment }.max { |lhs, rhs| lhs <=> rhs }
432
+ size = members.last.size + members.last.offset
433
+ aligned_size = Memory.align_size(size, alignment)
434
+ # Oddly enough, it would be easier to pass the encoding string into this
435
+ # function, but then it would ruin the nice little thing I have going where
436
+ # this function isn't dependent on parsing encodings, so we reproduce the
437
+ # encoding here as though it wasn't sitting just above us in the stack
438
+ # (which it might not be, but the chance of it is slim to none).
439
+ encoding = encode_member_info(members).freeze
440
+
441
+ Class.new(Memory) do |struct_klass|
442
+ # Set the class's constants, then include StructBase to define its members
443
+ # and other methods.
444
+ const_set(:ENCODING, encoding)
445
+ const_set(:MEMBERS, members)
446
+ const_set(:SIZE, size)
447
+ const_set(:ALIGNED_SIZE, aligned_size)
448
+ const_set(:ALIGNMENT, alignment)
667
449
 
668
- def free! # :nodoc:
669
- __free_cache__
670
- @length = 0
671
- super
672
- end
450
+ const_set(:MEMBERS_HASH, members.reduce({}) { |hash, member|
451
+ hash[member.name] = member
452
+ hash
453
+ })
673
454
 
455
+ const_set(:MEMBERS_GETFN, members.reduce({}) { |hash, member|
456
+ hash[member.name] = :"get_#{member.name}"
457
+ hash
458
+ })
674
459
 
675
- private
460
+ const_set(:MEMBERS_SETFN, members.reduce({}) { |hash, member|
461
+ hash[member.name] = :"set_#{member.name}"
462
+ hash
463
+ })
676
464
 
677
- def __free_cache__ # :nodoc:
678
- if @__cache__
679
- @__cache__.each { |entry|
680
- entry.free!
681
- entry.remove_instance_variable(:@__base_memory__)
682
- } # zeroes address, making it NULL
683
- @__cache__ = nil
684
- end
685
- end
686
465
 
466
+ private :realloc!
687
467
 
688
- def __build_cache__ # :nodoc:
689
- addr = self.address
690
- @__cache__ = (0...length).map { |index|
691
- wrapper = self.class::BASE.__wrap__(addr + index * self.class::BASE::SIZE, self.class::BASE::SIZE)
692
- # Make sure the wrapped object keeps the memory from being collected while it's in use
693
- wrapper.instance_variable_set(:@__base_memory__, self)
694
- wrapper
695
- }
696
- end
468
+ include StructBase
697
469
 
470
+ # Build and define the struct type's array class.
471
+ const_set(:Array, CStruct.build_array_type(self))
472
+ end
698
473
 
699
- class <<self # :nodoc: all
700
- alias_method :[], :new
701
- end
474
+ end
702
475
 
703
- end)
704
476
 
705
- def self.[](length) # :nodoc:
706
- self::Array.new(length)
707
- end
477
+ #
478
+ # :nodoc:
479
+ # Generates an array class for the given struct class. This is called by
480
+ # ::build_struct_type and so shouldn't be called manually.
481
+ #
482
+ def self.build_array_type(struct_klass)
483
+ Class.new(Memory) do |array_klass|
484
+ const_set(:BASE, struct_klass)
708
485
 
709
- end
486
+ private :realloc!
710
487
 
711
- if klass_name
712
- const_set(klass_name, klass)
713
- add_type(klass_name, klass)
714
- end
488
+ include StructArrayBase
489
+ end # Class.new
490
+ end # build_array_type
715
491
 
716
- klass
717
- end
718
492
 
719
493
  class <<self ; alias_method :[], :new ; end
720
494