snow-data 1.2.0 → 1.3.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: 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