snow-data 1.2.0 → 1.3.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: 40d3b634a2fe9ad1783ff57bd03dbdb9e01d28a4
4
- data.tar.gz: 0087c9dd8a0f26fbc239b518d66243ee4b391f48
3
+ metadata.gz: 4070dd82808151a7dcdd5e41c49fcd14e2dc7f14
4
+ data.tar.gz: dfaee4ffae49a3c3752f052a3089c6c0010afea2
5
5
  SHA512:
6
- metadata.gz: 7e8e6ec2a0160b4ba35170e73d4db301bb99a6af90a769f72ee75e8e1aef4d6ccb0708f4f84546330721ae134487261e8c8ac4dd2897cf9b56f8f229ed257c53
7
- data.tar.gz: a46085a0735417717a5af9a952027e1e711b292b0b76406dd2522e038ca21473bcd38e15562503747ae4c3f09430d5a418d7ea286c839bcf00e91af41dd7c6f3
6
+ metadata.gz: 264998e906e61221c5f4ad786a32bcf647b895b02d2af123f244da4a488ced5c2fba8f145b55c877963fca7ff10c14a7022763e9b0a33ecc2a84dbaa6be4696d
7
+ data.tar.gz: 4b64d995bb9c8ad6c4f187cb919ed3e673b478bdb8d5fb95b93354a6887e2488e16488c3daca301a79545df4003b7e324bcda0599e4181c2fcdd18e162b16796
@@ -79,7 +79,7 @@ class CStruct
79
79
  # CStruct.member_encoding(:foo, :float, 32, nil) # => "foo:float[32]:4"
80
80
  #
81
81
  def self.member_encoding(name, type, length: 1, alignment: nil)
82
- type = type.intern
82
+ type = type.to_sym
83
83
  alignment = alignment || ALIGNMENTS[type] || ALIGNMENTS[:*]
84
84
  raise ArgumentError, "Invalid length: #{length}. Must be > 0." if length < 1
85
85
  raise ArgumentError, "Invalid alignment: #{alignment}. Must be a power of two." if ! power_of_two?(alignment)
@@ -111,6 +111,9 @@ class CStruct
111
111
  (?<type_alignment_decl> \s* \: \s* # 4
112
112
  (?<type_alignment> \d+ ) # 5
113
113
  )?
114
+ (?<offset_decl> \s* @ \s* # 6
115
+ (?<offset> \d+ ) # 7
116
+ )?
114
117
  \s* (?: ; | $ | \n) # terminator
115
118
  }mx
116
119
 
@@ -280,7 +283,7 @@ class CStruct
280
283
  define_method(setter) do |offset, data|
281
284
  raise "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
282
285
  local_addr = self.address + offset
283
- if !data.respond_to?(:address) || local_addr != data.address
286
+ if ! data.respond_to?(:address) || local_addr != data.address
284
287
  copy!(data, offset, 0, klass::SIZE)
285
288
  end
286
289
 
@@ -332,11 +335,12 @@ class CStruct
332
335
  # Encodings are how you define C structs using Snow::CStruct. It's a fairly
333
336
  # simple string format, defined as such:
334
337
  #
338
+ # offset ::= '@' integer
335
339
  # length ::= '[' integer ']'
336
340
  # alignment ::= ':' integer
337
341
  # typename ::= ':' Name
338
342
  # member_name ::= Name
339
- # member_decl ::= member_name typename [ length ] [ alignment ]
343
+ # member_decl ::= member_name typename [ length ] [ alignment ] [ offset ]
340
344
  #
341
345
  # So, for example, the encoding string "foo: float[4]:8" defines a C struct
342
346
  # with a single member, `foo`, which is an array of 4 32-bit floats with an
@@ -344,6 +348,12 @@ class CStruct
344
348
  # size (e.g., "foo: float" would be algiend to 4 bytes) and all members have a
345
349
  # length of 1 unless specified otherwise.
346
350
  #
351
+ # Offsets should only be specified if you absolutely know what you're doing,
352
+ # otherwise you may break certain things (for example, native sizing on ints).
353
+ # In addition, an offset can be provided to simulate union-like behavior for
354
+ # some members, though you are better off using the Builder methods to define
355
+ # a union than you are via an encoding string.
356
+ #
347
357
  # A list of all types follows, including their short and long names, and their
348
358
  # corresponding types in C. Short names are only provided for convenience and
349
359
  # are generally not too useful except for reducing string length. They're
@@ -423,23 +433,98 @@ class CStruct
423
433
  raise ArgumentError, "wrong number of arguments (#{args.length} for 0..2)"
424
434
  end
425
435
 
426
- klass_name = klass_name.intern if klass_name
427
-
428
436
  members = if block_given?
429
437
  Builder.new(&block).member_info
430
438
  else
431
439
  decode_member_info(encoding)
432
440
  end
433
441
 
434
- raise "No valid members found in encoding" if members.empty?
442
+ __define_struct__(klass_name, build_struct_type(members))
443
+ end
435
444
 
436
- klass = build_struct_type(members)
437
445
 
438
- if klass_name
439
- const_set(klass_name, klass)
440
- add_type(klass_name, klass)
441
- end
446
+ #
447
+ # :nodoc:
448
+ #
449
+ # Passes a block through to a Builder and provides a flag for whether the root
450
+ # level of the builder is a struct or union.
451
+ #
452
+ def self.__build_struct__(name, is_union, &block)
453
+ members = Builder.new(is_union: is_union, &block).member_info
454
+ __define_struct__(name, build_struct_type(members))
455
+ end
456
+
457
+
458
+ #
459
+ # call-seq:
460
+ # union { ... } => Class
461
+ # union(name) { ... } => Class
462
+ #
463
+ # Defines a union with a block for declaring the members of the union using
464
+ # Builder methods. Unions are basically structs whose root members occupy the
465
+ # same or adjacent locations in memory. You may use Builder#struct to define
466
+ # multiple internal unnamed structs inside a union.
467
+ #
468
+ # If a name is provided, the resulting class will be added as a constant under
469
+ # CStruct and it will be added as a type recognized in other CStruct-defined
470
+ # structs and unions.
471
+ #
472
+ # ### Example
473
+ #
474
+ # CStruct.union {
475
+ # # size, name, and some_value all share the same memory in the union
476
+ # # so set_name will modify size and some_value, and any transposition
477
+ # # of those names is also true.
478
+ # size_t :size
479
+ # uint32_t :name
480
+ # double :some_value
481
+ # }
482
+ #
483
+ def self.union(name = nil, &block)
484
+ __build_struct__(name, true, &block)
485
+ end
486
+
442
487
 
488
+ #
489
+ # call-seq:
490
+ # struct { ... } => Class
491
+ # struct(name) { ... } => Class
492
+ #
493
+ # Defines a struct with a block for declaring the members of the struct using
494
+ # Builder methods. Members of structs do not share space, unlike unions.
495
+ #
496
+ # If a name is provided, the resulting class will be added as a constant under
497
+ # CStruct and it will be added as a type recognized in other CStruct-defined
498
+ # structs and unions.
499
+ #
500
+ # ### Example
501
+ #
502
+ # CStruct.struct {
503
+ # # size, red, green, and blue all get their own memory in the struct,
504
+ # # one after the other. Modifying one value will not modify another.
505
+ # size_t :size
506
+ # uint16_t :red[256]
507
+ # uint16_t :green[256]
508
+ # uint16_t :blue[256]
509
+ # }
510
+ #
511
+ def self.struct(name = nil, &block)
512
+ __build_struct__(name, false, &block)
513
+ end
514
+
515
+
516
+ #
517
+ # :nodoc:
518
+ #
519
+ # Used by ::__build_struct__ and ::new to handle defining a struct class's
520
+ # constant and adding its type to those recognized by CStruct.
521
+ #
522
+ def self.__define_struct__(name, klass)
523
+ if name
524
+ name = name.to_sym
525
+ const_set(name, klass)
526
+ add_type(name, klass)
527
+ end
443
528
  klass
444
529
  end
445
530
 
@@ -455,12 +540,13 @@ class CStruct
455
540
  total_size = 0
456
541
  encoding.scan(ENCODING_REGEX).map do
457
542
  |match|
458
- name = match[0].intern
459
- type = real_type_of(match[1].intern)
543
+ name = match[0].to_sym
544
+ type = real_type_of(match[1].to_sym)
460
545
  length = (match[3] || 1).to_i
461
546
  align = (match[5] || ALIGNMENTS[type] || 1).to_i
462
547
  size = SIZES[type] * length
463
- offset = Memory.align_size(total_size, align)
548
+ offset = (match[7] || 0).to_i
549
+ offset += Memory.align_size(total_size, align) if ! match[7]
464
550
  total_size = offset + size
465
551
 
466
552
  StructMemberInfo[name, type, size, length, align, offset]
@@ -476,7 +562,7 @@ class CStruct
476
562
  #
477
563
  def self.encode_member_info(members)
478
564
  members.map { |member|
479
- "#{member.name}:#{member.type}[#{member.length}]:#{member.alignment}"
565
+ "#{member.name}:#{member.type}[#{member.length}]:#{member.alignment}@#{member.offset}"
480
566
  }.join(?;)
481
567
  end
482
568
 
@@ -498,8 +584,8 @@ class CStruct
498
584
  members.freeze
499
585
 
500
586
  # Get the alignment, size, aligned size, and encoding of the struct.
501
- alignment = members.map { |member| member.alignment }.max { |lhs, rhs| lhs <=> rhs }
502
- type_size = members.last.size + members.last.offset
587
+ alignment = members.map(&:alignment).max
588
+ type_size = members.map { |m| m.offset + m.size }.max
503
589
  aligned_size = Memory.align_size(type_size, alignment)
504
590
  # Oddly enough, it would be easier to pass the encoding string into this
505
591
  # function, but then it would ruin the nice little thing I have going where
@@ -1,12 +1,35 @@
1
+ # This file is part of ruby-snowdata.
2
+ # Copyright (c) 2013 Noel Raymond Cower. All rights reserved.
3
+ # See COPYING for license details.
4
+
1
5
  require 'set'
2
6
 
3
7
  module Snow ; end
4
8
  class Snow::CStruct ; end
5
9
 
10
+ #
11
+ # Utility classes used by CStruct to create member info structs. Exposed to the
12
+ # user via instance_exec blocks for declaring struct/union members.
13
+ #
14
+ # See CStruct::new, CStruct::struct, or CStruct::union.
15
+ #
6
16
  class Snow::CStruct::Builder
7
17
 
18
+ #
19
+ # Struct describing a level of a C struct. Contains a flag for whether the
20
+ # level is a union, its offset, alignment, size, and its members (which may
21
+ # include descendant levels).
22
+ #
23
+ MemberStackLevel = Struct.new(:is_union, :offset, :alignment, :size, :members)
24
+
25
+
26
+ #
27
+ # A Set of symbols for all types whose declaration methods have already been
28
+ # defined by ::flush_type_methods!.
29
+ #
8
30
  @@defined_types = Set.new
9
31
 
32
+
10
33
  #
11
34
  # call-seq:
12
35
  # new => Builder
@@ -16,23 +39,69 @@ class Snow::CStruct::Builder
16
39
  # given, then it will be instance_exec'd, allowing you to easily call any
17
40
  # declaration methods on the builder instance.
18
41
  #
19
- def initialize(&block)
20
- @members = []
21
- @last_offset = 0
42
+ def initialize(is_union: false, &block)
43
+ @member_names = Set.new
44
+ @level = MemberStackLevel[!!is_union, 0, 1, 0, []]
22
45
  instance_exec(&block) if block_given?
46
+ @members = []
47
+ self.class.adjust_level(@level)
48
+ @level.members.each { |member| member.freeze }
49
+ @members = self.class.flatten_level(@level)
50
+ @members.each(&:freeze)
51
+ @members.freeze
52
+ end
53
+
54
+
55
+ #
56
+ # Flattens a MemberStackLevel's members into a single array and returns an
57
+ # array of StructMemberInfo objects.
58
+ #
59
+ def self.flatten_level(level, info_buffer = [])
60
+ level.members.each { |m|
61
+ if m.kind_of?(MemberStackLevel)
62
+ flatten_level(m, info_buffer)
63
+ else
64
+ info_buffer.push(m)
65
+ end
66
+ }
67
+ info_buffer
68
+ end
69
+
70
+
71
+ #
72
+ # Iterates over a level's members, including sub-levels, and adjusts their
73
+ # offsets and the level's size accordingly. This is essentially the processing
74
+ # phase done to give members their offsets and levels their sizes.
75
+ #
76
+ def self.adjust_level(level, start_at: 0)
77
+ base_offset = offset = ::Snow::Memory.align_size(start_at, level.alignment)
78
+ level.size = 0
79
+ level.members.each do |m|
80
+ m.offset = offset = ::Snow::Memory.align_size(offset, m.alignment)
81
+ if m.kind_of?(MemberStackLevel)
82
+ adjust_level(m, start_at: offset)
83
+ end
84
+ if level.is_union
85
+ level.size = [level.size, (m.offset + m.size) - base_offset].max
86
+ else
87
+ level.size = (m.offset + m.size) - base_offset
88
+ offset += m.size
89
+ end
90
+ end
23
91
  end
24
92
 
93
+
25
94
  #
26
95
  # call-seq:
27
96
  # member_info => [StructMemberInfo]
28
97
  #
29
- # Returns the StructMemberInfo array for the builder. You should copy this if
30
- # you need to modify it, and the same goes for its elements.
98
+ # Returns the StructMemberInfo array for the builder.
31
99
  #
32
100
  def member_info
33
101
  @members
34
102
  end
35
103
 
104
+
36
105
  #
37
106
  # call-seq:
38
107
  # flush_type_methods! => self
@@ -55,7 +124,13 @@ class Snow::CStruct::Builder
55
124
  method_name = method_name.to_sym
56
125
 
57
126
  __send__(:define_method, method_name) do | name, lengths = 1, align: nil |
127
+ level = instance_variable_get(:@level)
128
+
129
+ member_names = instance_variable_get(:@member_names)
130
+
58
131
  name = name.to_sym
132
+ raise ArgumentError, "#{name} redefined in struct" if member_names.include?(name)
133
+ member_names.add(name)
59
134
 
60
135
  length = case lengths
61
136
  when Integer, Fixnum then lengths
@@ -69,15 +144,21 @@ class Snow::CStruct::Builder
69
144
  align = (align || ::Snow::CStruct::ALIGNMENTS[type_name]).to_i
70
145
  raise "Nil alignment for type #{type_name}" if align.nil?
71
146
 
72
- last_offset = instance_variable_get(:@last_offset)
73
- offset = ::Snow::Memory.align_size(last_offset, align)
74
- last_offset = offset + size
147
+ base_offset = level.offset
148
+ offset = ::Snow::Memory.align_size(base_offset, align)
149
+ level.offset = offset + size unless level.is_union
75
150
 
76
151
  member_info = ::Snow::CStruct::StructMemberInfo[
77
152
  name, type_name, size, length, align, offset]
78
153
 
79
- instance_variable_set(:@last_offset, last_offset)
80
- instance_variable_get(:@members).push(member_info.freeze)
154
+ level.alignment = [level.alignment, align].max
155
+ if level.is_union
156
+ level.size = [level.size, size].max
157
+ else
158
+ level.size += (offset - base_offset) + size
159
+ end
160
+
161
+ level.members.push(member_info)
81
162
  end # define_method(type_name)
82
163
 
83
164
  @@defined_types.add(type_name)
@@ -91,4 +172,73 @@ class Snow::CStruct::Builder
91
172
  self
92
173
  end # flush_type_methods!
93
174
 
175
+
176
+ #
177
+ # Creates a new member level for the builder and instance_exec-s the block,
178
+ # then passes its alignment onto its ancestor level.
179
+ #
180
+ def __do_level__(align: nil, is_union: false, &block)
181
+ parent = @level
182
+ next_level = MemberStackLevel[!!is_union, 0, 1, 0, []]
183
+ @level = next_level
184
+
185
+ self.instance_exec(&block)
186
+
187
+ next_level.alignment = align || next_level.alignment
188
+ parent.alignment = [parent.alignment, next_level.alignment].max
189
+
190
+ @level = parent
191
+ parent.members.push(next_level)
192
+ end
193
+
194
+
195
+ #
196
+ # call-seq:
197
+ # union { ... }
198
+ # union(align: nil) { ... }
199
+ #
200
+ # For the scope of the block, any members declared are considered union
201
+ # members, as opposed to struct members. Each member of a union occupies the
202
+ # same space as other members of the union, though their offsets may differ
203
+ # if their alignments differ as well.
204
+ #
205
+ # If no alignment is specified for the union, its base offset will be aligned
206
+ # to that of the member with the largest alignment. Otherwise, if an alignment
207
+ # is specified, members may not occupy the same offsets relative to the
208
+ # beginning of the union.
209
+ #
210
+ # For example, if a union with an alignment of 4 has uint32_t and uint64_t
211
+ # members with default alignments with a starting offset of 4, the uint32_t
212
+ # member will be located at offset 4, while the uint64_t member will be at
213
+ # offset 8. As such, it's best to leave union alignments at their default
214
+ # unless absolutely necessary.
215
+ #
216
+ def union(align: nil, &block)
217
+ __do_level__(align: align, is_union: true, &block)
218
+ end
219
+
220
+
221
+ #
222
+ # call-seq:
223
+ # struct { ... }
224
+ # struct(align: nil) { ... }
225
+ #
226
+ # For the scope of the block, any members declared are considered struct
227
+ # members, as opposed to union members.
228
+ #
229
+ # Each member of a struct occupies its own space inside the struct, unlike a
230
+ # union (where each member occupies either the same or adjacent space in the
231
+ # union).
232
+ #
233
+ # Unless an alignment is specified, the default alignment of a struct is that
234
+ # of the largest alignment of all its members. If specifying an alignment,
235
+ # keep in mind that the members of the struct may need to also be manually
236
+ # aligned, otherwise the first member may be preceeded by padding bytes
237
+ # regardless of the start of the struct. See #union for more information on
238
+ # alignment oddities.
239
+ #
240
+ def struct(align: nil, &block)
241
+ __do_level__(align: align, is_union: false, &block)
242
+ end
243
+
94
244
  end
@@ -7,6 +7,6 @@ module Snow
7
7
  #
8
8
  # The snow-data version string.
9
9
  #
10
- SNOW_DATA_VERSION = '1.2.0'.freeze
10
+ SNOW_DATA_VERSION = '1.3.0'.freeze
11
11
 
12
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snow-data
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noel Raymond Cower