snow-data 1.0.0 → 1.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.
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