@@ -52,151 +52,211 @@ module Fields
52
52
:tool_entries => "L=" ,
53
53
} . freeze
54
54
55
- # a list of classes that must be initialized separately
56
- # in the constructor
57
- CLASS_LIST = %i[ lcstr two_level_hints_table tool_entries ] . freeze
55
+ # A list of classes that must get initialized
56
+ # To add a new class append it here and add the init method to the def_class_reader method
57
+ # @api private
58
+ CLASS_LIST = %i[ lcstr tool_entries two_level_hints_table ] . freeze
58
59
end
59
60
60
- # map of field names to field types
61
- @type_map = { }
61
+ # map of field names to indices
62
+ @field_idxs = { }
62
63
63
- # array of field name in definition order
64
- @field_list = [ ]
64
+ # array of fields sizes
65
+ @size_list = [ ]
65
66
66
- # map of field options
67
- @option_map = { }
67
+ # array of field format codes
68
+ @fmt_list = [ ]
68
69
69
70
# minimum number of required arguments
70
71
@min_args = 0
71
72
72
- class << self
73
- # Public getters
74
- attr_reader :type_map , :field_list , :option_map , :min_args
75
-
76
- private
77
-
78
- # Private setters
79
- attr_writer :type_map , :field_list , :option_map , :min_args
80
- end
81
-
82
73
# Used to dynamically create an instance of the inherited class
83
74
# according to the defined fields.
84
75
# @param args [Array[Value]] list of field parameters
85
76
def initialize ( *args )
86
77
raise ArgumentError , "Invalid number of arguments" if args . size < self . class . min_args
87
78
88
- # Set up all instance variables
89
- self . class . field_list . zip ( args ) . each do |field , value |
90
- # TODO: Find a better way to specialize initialization for certain types
79
+ @values = args
80
+ end
81
+
82
+ # @return [Hash] a hash representation of this {MachOStructure}.
83
+ def to_h
84
+ {
85
+ "structure" => {
86
+ "format" => self . class . format ,
87
+ "bytesize" => self . class . bytesize ,
88
+ } ,
89
+ }
90
+ end
91
+
92
+ class << self
93
+ attr_reader :min_args
94
+
95
+ # @param endianness [Symbol] either `:big` or `:little`
96
+ # @param bin [String] the string to be unpacked into the new structure
97
+ # @return [MachO::MachOStructure] the resulting structure
98
+ # @api private
99
+ def new_from_bin ( endianness , bin )
100
+ format = Utils . specialize_format ( self . format , endianness )
101
+
102
+ new ( *bin . unpack ( format ) )
103
+ end
104
+
105
+ def format
106
+ @format ||= @fmt_list . join
107
+ end
108
+
109
+ def bytesize
110
+ @bytesize ||= @size_list . sum
111
+ end
112
+
113
+ private
114
+
115
+ # @param subclass [Class] subclass type
116
+ # @api private
117
+ def inherited ( subclass ) # rubocop:disable Lint/MissingSuper
118
+ # Clone all class instance variables
119
+ field_idxs = @field_idxs . dup
120
+ size_list = @size_list . dup
121
+ fmt_list = @fmt_list . dup
122
+ min_args = @min_args . dup
123
+
124
+ # Add those values to the inheriting class
125
+ subclass . class_eval do
126
+ @field_idxs = field_idxs
127
+ @size_list = size_list
128
+ @fmt_list = fmt_list
129
+ @min_args = min_args
130
+ end
131
+ end
132
+
133
+ # @param name [Symbol] name of internal field
134
+ # @param type [Symbol] type of field in terms of binary size
135
+ # @param options [Hash] set of additonal options
136
+ # Expected options
137
+ # :size [Integer] size in bytes
138
+ # :mask [Integer] bitmask
139
+ # :unpack [String] string format
140
+ # :default [Value] default value
141
+ # @api private
142
+ def field ( name , type , **options )
143
+ raise ArgumentError , "Invalid field type #{ type } " unless Fields ::FORMAT_CODE . key? ( type )
144
+
145
+ idx = if @field_idxs . key? ( name )
146
+ @field_idxs [ name ]
147
+ else
148
+ @min_args += 1 unless options . key? ( :default )
149
+ @field_idxs [ name ] = @field_idxs . size
150
+ @size_list << nil
151
+ @fmt_list << nil
152
+ @field_idxs . size - 1
153
+ end
154
+
155
+ @size_list [ idx ] = Fields ::BYTE_SIZE [ type ] || options [ :size ]
156
+ @fmt_list [ idx ] = Fields ::FORMAT_CODE [ type ]
157
+ @fmt_list [ idx ] += options [ :size ] . to_s if options . key? ( :size )
91
158
92
- # Handle special cases
93
- type = self . class . type_map [ field ]
159
+ # Generate methods
94
160
if Fields ::CLASS_LIST . include? ( type )
95
- case type
96
- when :lcstr
97
- value = LoadCommands ::LoadCommand ::LCStr . new ( self , value )
98
- when :two_level_hints_table
99
- value = LoadCommands ::TwolevelHintsCommand ::TwolevelHintsTable . new ( view , htoffset , nhints )
100
- when :tool_entries
101
- value = LoadCommands ::BuildVersionCommand ::ToolEntries . new ( view , value )
161
+ def_class_reader ( name , type , idx )
162
+ elsif options . key? ( :mask )
163
+ def_mask_reader ( name , idx , options [ :mask ] )
164
+ elsif options . key? ( :unpack )
165
+ def_unpack_reader ( name , idx , options [ :unpack ] )
166
+ elsif options . key? ( :default )
167
+ def_default_reader ( name , idx , options [ :default ] )
168
+ else
169
+ def_reader ( name , idx )
170
+ end
171
+ end
172
+
173
+ #
174
+ # Method Generators
175
+ #
176
+
177
+ # Generates a reader method for classes that need to be initialized.
178
+ # These classes are defined in the Fields::CLASS_LIST array.
179
+ # @param name [Symbol] name of internal field
180
+ # @param type [Symbol] type of field in terms of binary size
181
+ # @param idx [Integer] the index of the field value in the @values array
182
+ # @api private
183
+ def def_class_reader ( name , type , idx )
184
+ case type
185
+ when :lcstr
186
+ define_method ( name ) do
187
+ instance_variable_defined? ( "@#{ name } " ) ||
188
+ instance_variable_set ( "@#{ name } " , LoadCommands ::LoadCommand ::LCStr . new ( self , @values [ idx ] ) )
189
+
190
+ instance_variable_get ( "@#{ name } " )
102
191
end
103
- elsif self . class . option_map . key? ( field )
104
- options = self . class . option_map [ field ]
105
-
106
- if options . key? ( :mask )
107
- value &= ~options [ :mask ]
108
- elsif options . key? ( :unpack )
109
- value = value . unpack ( options [ :unpack ] )
110
- elsif value . nil? && options . key? ( :default )
111
- value = options [ :default ]
192
+ when :two_level_hints_table
193
+ define_method ( name ) do
194
+ instance_variable_defined? ( "@#{ name } " ) ||
195
+ instance_variable_set ( "@#{ name } " , LoadCommands ::TwolevelHintsCommand ::TwolevelHintsTable . new ( view , htoffset , nhints ) )
196
+
197
+ instance_variable_get ( "@#{ name } " )
112
198
end
113
- end
199
+ when :tool_entries
200
+ define_method ( name ) do
201
+ instance_variable_defined? ( "@#{ name } " ) ||
202
+ instance_variable_set ( "@#{ name } " , LoadCommands ::BuildVersionCommand ::ToolEntries . new ( view , @values [ idx ] ) )
114
203
115
- instance_variable_set ( "@#{ field } " , value )
204
+ instance_variable_get ( "@#{ name } " )
205
+ end
206
+ end
116
207
end
117
- end
118
208
119
- # @param subclass [Class] subclass type
120
- # @api private
121
- def self . inherited ( subclass ) # rubocop:disable Lint/MissingSuper
122
- # Clone all class instance variables
123
- type_map = @type_map . dup
124
- field_list = @field_list . dup
125
- option_map = @option_map . dup
126
- min_args = @min_args . dup
127
-
128
- # Add those values to the inheriting class
129
- subclass . class_eval do
130
- @type_map = type_map
131
- @field_list = field_list
132
- @option_map = option_map
133
- @min_args = min_args
134
- end
135
- end
209
+ # Generates a reader method for fields that need to be bitmasked.
210
+ # @param name [Symbol] name of internal field
211
+ # @param idx [Integer] the index of the field value in the @values array
212
+ # @param mask [Integer] the bitmask
213
+ # @api private
214
+ def def_mask_reader ( name , idx , mask )
215
+ define_method ( name ) do
216
+ instance_variable_defined? ( "@#{ name } " ) ||
217
+ instance_variable_set ( "@#{ name } " , @values [ idx ] & ~mask )
136
218
137
- # @param name [Symbol] name of internal field
138
- # @param type [Symbol] type of field in terms of binary size
139
- # @param options [Hash] set of additonal options
140
- # Expected options
141
- # :size [Integer] size in bytes
142
- # :mask [Integer] bitmask
143
- # :unpack [String] string format
144
- # :default [Value] default value
145
- # @api private
146
- def self . field ( name , type , **options )
147
- raise ArgumentError , "Invalid field type #{ type } " unless Fields ::FORMAT_CODE . key? ( type )
148
-
149
- if type_map . key? ( name )
150
- @min_args -= 1 unless @option_map . dig ( name , :default )
151
-
152
- @option_map . delete ( name ) if options . empty?
153
- else
154
- attr_reader name
155
-
156
- # TODO: Should be able to generate #to_s based on presence of LCStr which is the 90% case
157
- # TODO: Could try generating #to_h for the 90% perecent case
158
- # Might be best to make another functional called maybe generate
159
-
160
- @field_list << name
219
+ instance_variable_get ( "@#{ name } " )
220
+ end
161
221
end
162
222
163
- @option_map [ name ] = options unless options . empty?
164
- @min_args += 1 unless options . key? ( :default )
165
- @type_map [ name ] = type
166
- end
167
-
168
- # @param endianness [Symbol] either `:big` or `:little`
169
- # @param bin [String] the string to be unpacked into the new structure
170
- # @return [MachO::MachOStructure] the resulting structure
171
- # @api private
172
- def self . new_from_bin ( endianness , bin )
173
- format = Utils . specialize_format ( self . format , endianness )
223
+ # Generates a reader method for fields that need further unpacking.
224
+ # @param name [Symbol] name of internal field
225
+ # @param idx [Integer] the index of the field value in the @values array
226
+ # @param unpack [String] the format code used for futher binary unpacking
227
+ # @api private
228
+ def def_unpack_reader ( name , idx , unpack )
229
+ define_method ( name ) do
230
+ instance_variable_defined? ( "@#{ name } " ) ||
231
+ instance_variable_set ( "@#{ name } " , @values [ idx ] . unpack ( unpack ) )
174
232
175
- new ( *bin . unpack ( format ) )
176
- end
233
+ instance_variable_get ( "@#{ name } " )
234
+ end
235
+ end
177
236
178
- def self . format
179
- @format ||= field_list . map do |field |
180
- Fields ::FORMAT_CODE [ type_map [ field ] ] +
181
- option_map . dig ( field , :size ) . to_s
182
- end . join
183
- end
237
+ # Generates a reader method for fields that have default values.
238
+ # @param name [Symbol] name of internal field
239
+ # @param idx [Integer] the index of the field value in the @values array
240
+ # @param default [Value] the default value
241
+ # @api private
242
+ def def_default_reader ( name , idx , default )
243
+ define_method ( name ) do
244
+ instance_variable_defined? ( "@#{ name } " ) ||
245
+ instance_variable_set ( "@#{ name } " , @values . size > idx ? @values [ idx ] : default )
184
246
185
- def self . bytesize
186
- @bytesize ||= field_list . map do |field |
187
- Fields ::BYTE_SIZE [ type_map [ field ] ] ||
188
- option_map . dig ( field , :size )
189
- end . sum
190
- end
247
+ instance_variable_get ( "@#{ name } " )
248
+ end
249
+ end
191
250
192
- # @return [Hash] a hash representation of this {MachOStructure}.
193
- def to_h
194
- {
195
- "structure" => {
196
- "format" => self . class . format ,
197
- "bytesize" => self . class . bytesize ,
198
- } ,
199
- }
251
+ # Generates an attr_reader like method for a field.
252
+ # @param name [Symbol] name of internal field
253
+ # @param idx [Integer] the index of the field value in the @values array
254
+ # @api private
255
+ def def_reader ( name , idx )
256
+ define_method ( name ) do
257
+ @values [ idx ]
258
+ end
259
+ end
200
260
end
201
261
end
202
262
end
0 commit comments