wrapture 0.2.2 → 0.5.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
  SHA256:
3
- metadata.gz: 5a6c087443d51b13a641bf0810c398c073415422de2a3a5cc715bc75ab265230
4
- data.tar.gz: a2d2175fe6d0ffbdc5e1a878fda7c9ed3db8dd8d0ae8d2c88b143167bf76b0a8
3
+ metadata.gz: 5a2ff7411136ffd84e6078406f6a4a8bf76cfaf0c2f1398d9409d019d29a6b8a
4
+ data.tar.gz: 8c91f45af881ad5f7f28b184d5d9d63ce9cabde0320af43303efb51764a2d458
5
5
  SHA512:
6
- metadata.gz: e722abc8af3af03e5e98189c034a3d60df5f1814776d127cee8be5bebdc9d87a1226f4b305e1b083da38c03a9ad239e98e1572b75f1b5bb9dd4d5bc5e93a95ea
7
- data.tar.gz: e26c2a12e04af2b22172de83b31246846f6a73780a46987604748da9232b9191f5204c94ead1b3016db55b8c6b29e2d1e193629abfee0a1a18bd63d592e97f9f
6
+ metadata.gz: cfb9b375f81fbfb6e75af4f52882fcff232ff5a9757732deb3446a1ae5b6de39cb1d4ddc2fd93d0c31f9fa3d9344e917119ad428a2040c2ec6415cdb002761ea
7
+ data.tar.gz: 936c998fd990eec6b580257b50a4e2d440ff9d6670c62250d1a38c6e42b7f70c1c7f8c02113a572388cd74beeeef7d235566d9bf89d90d028905eece199d2bc7
@@ -1,13 +1,46 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
3
5
  # frozen_string_literal: true
4
6
 
7
+ # Copyright 2019-2020 Joel E. Anderson
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+
5
21
  require 'yaml'
6
22
  require 'wrapture'
7
23
 
8
- spec = YAML.load_file ARGV[0]
24
+ scope = Wrapture::Scope.new
25
+
26
+ ARGV.each do |spec_file|
27
+ spec = YAML.load_file(spec_file)
28
+
29
+ if spec.key?('version') && !Wrapture.supports_version?(spec['version'])
30
+ raise Wrapture::UnsupportedSpecVersion
31
+ end
9
32
 
10
- spec['classes'].each do |spec_hash|
11
- class_spec = Wrapture::ClassSpec.new spec_hash
12
- class_spec.generate_wrappers
33
+ spec.fetch('templates', []).each do |temp_spec|
34
+ scope << Wrapture::TemplateSpec.new(temp_spec)
35
+ end
36
+
37
+ spec.fetch('classes', []).each do |class_spec|
38
+ scope.add_class_spec_hash(class_spec)
39
+ end
40
+
41
+ spec.fetch('enums', []).each do |enum_spec|
42
+ scope.add_enum_spec_hash(enum_spec)
43
+ end
13
44
  end
45
+
46
+ scope.generate_wrappers
@@ -1,10 +1,38 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
1
3
  # frozen_string_literal: true
2
4
 
3
- ##
5
+ # Copyright 2019-2020 Joel E. Anderson
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
4
19
  # Classes and functions for generating language wrappers
5
20
  module Wrapture
21
+ require 'wrapture/action_spec'
22
+ require 'wrapture/comment'
6
23
  require 'wrapture/constant_spec'
24
+ require 'wrapture/constants'
7
25
  require 'wrapture/class_spec'
26
+ require 'wrapture/enum_spec'
27
+ require 'wrapture/errors'
8
28
  require 'wrapture/function_spec'
29
+ require 'wrapture/normalize'
30
+ require 'wrapture/rule_spec'
31
+ require 'wrapture/param_spec'
32
+ require 'wrapture/scope'
33
+ require 'wrapture/struct_spec'
34
+ require 'wrapture/template_spec'
35
+ require 'wrapture/type_spec'
9
36
  require 'wrapture/version'
37
+ require 'wrapture/wrapped_function_spec'
10
38
  end
@@ -0,0 +1,80 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # Copyright 2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
20
+
21
+ module Wrapture
22
+ # An action to take within a generated program.
23
+ class ActionSpec
24
+ # Normalizes a hash specification of a rule. Normalization checks for
25
+ # invalid keys and unrecognized conditions.
26
+ def self.normalize_spec_hash(spec)
27
+ normalized = spec.dup
28
+
29
+ required_keys = %w[name constructor]
30
+
31
+ missing_keys = required_keys - spec.keys
32
+ unless missing_keys.empty?
33
+ missing_msg = "required keys are missing: #{missing_keys.join(', ')}"
34
+ raise(MissingSpecKey, missing_msg)
35
+ end
36
+
37
+ extra_keys = spec.keys - required_keys
38
+ unless extra_keys.empty?
39
+ extra_msg = "these keys are unrecognized: #{extra_keys.join(', ')}"
40
+ raise(InvalidSpecKey, extra_msg)
41
+ end
42
+
43
+ func_spec = WrappedFunctionSpec.normalize_spec_hash(spec['constructor'])
44
+ normalized['constructor'] = func_spec
45
+
46
+ normalized
47
+ end
48
+
49
+ # Creates an action spec based on the provided spec hash.
50
+ #
51
+ # The hash must have the following keys:
52
+ # name:: the type of action to take (currently only throw-exception is
53
+ # supported)
54
+ # constructor:: the function to use to create the exception, described as a
55
+ # wrapped function call
56
+ def initialize(spec)
57
+ @spec = ActionSpec.normalize_spec_hash(spec)
58
+ end
59
+
60
+ # A list of includes needed for the action.
61
+ def includes
62
+ @spec['constructor']['includes'].dup
63
+ end
64
+
65
+ # A string containing the invocation of this action.
66
+ def take
67
+ call_spec = @spec['constructor']
68
+
69
+ params = call_spec['params'].map do |param_spec|
70
+ if param_spec['value'] == RETURN_VALUE_KEYWORD
71
+ 'return_val'
72
+ else
73
+ param_spec['value']
74
+ end
75
+ end
76
+
77
+ "throw #{call_spec['name']}( #{params.join(', ')} )"
78
+ end
79
+ end
80
+ end
@@ -1,203 +1,220 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
1
3
  # frozen_string_literal: true
2
4
 
3
- require 'wrapture/constant_spec'
4
- require 'wrapture/function_spec'
5
+ #--
6
+ # Copyright 2019-2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
5
20
 
6
21
  module Wrapture
7
- ##
8
22
  # A description of a class, including its constants, functions, and other
9
23
  # details.
10
24
  class ClassSpec
11
- def self.typed_variable(type, name)
12
- if type.end_with? '*'
13
- "#{type}#{name}"
14
- else
15
- "#{type} #{name}"
16
- end
17
- end
25
+ # Normalizes a hash specification of a class. Normalization will check for
26
+ # things like invalid keys, duplicate entries in include lists, and will set
27
+ # missing keys to their default values (for example, an empty list if no
28
+ # includes are given).
29
+ #
30
+ # If this spec cannot be normalized, for example because it is invalid or
31
+ # it uses an unsupported version type, then an exception is raised.
32
+ def self.normalize_spec_hash(spec)
33
+ raise NoNamespace unless spec.key?('namespace')
34
+ raise MissingSpecKey, 'name key is required' unless spec.key?('name')
18
35
 
19
- def initialize(spec)
20
- @spec = ClassSpec.normalize_spec_hash(spec)
36
+ Comment.validate_doc(spec['doc']) if spec.key?('doc')
21
37
 
22
- @functions = []
23
- @spec['functions'].each do |function_spec|
24
- @functions << FunctionSpec.new(function_spec, self)
25
- end
38
+ normalized = spec.dup
39
+ normalized.default = []
40
+
41
+ normalized['version'] = Wrapture.spec_version(spec)
42
+ normalized['includes'] = Wrapture.normalize_includes(spec['includes'])
43
+ normalized['type'] = ClassSpec.effective_type(normalized)
26
44
 
27
- @constants = []
28
- @spec['constants'].each do |constant_spec|
29
- @constants << ConstantSpec.new(constant_spec)
45
+ if spec.key?('parent')
46
+ includes = Wrapture.normalize_includes(spec['parent']['includes'])
47
+ normalized['parent']['includes'] = includes
30
48
  end
31
- end
32
49
 
33
- def generate_wrappers
34
- files = []
35
- files << generate_declaration_file
36
- files << generate_definition_file
50
+ normalized
37
51
  end
38
52
 
39
- def resolve_param(param)
40
- case param
41
- when 'equivalent-struct'
42
- equivalent_struct
43
- when 'equivalent-struct-pointer'
44
- equivalent_struct_pointer
45
- else
46
- param
53
+ # Gives the effective type of the given class spec hash.
54
+ def self.effective_type(spec)
55
+ inferred_pointer_wrapper = spec['constructors'].any? do |func|
56
+ func['wrapped-function']['return']['type'] == EQUIVALENT_POINTER_KEYWORD
47
57
  end
48
- end
49
58
 
50
- def function_call(spec)
51
- resolved_params = []
59
+ if spec.key?('type')
60
+ valid_types = %w[pointer struct]
61
+ unless valid_types.include?(spec['type'])
62
+ type_message = "#{spec['type']} is not a valid class type"
63
+ raise InvalidSpecKey.new(type_message, valid_keys: valid_types)
64
+ end
52
65
 
53
- spec['params'].each do |param|
54
- resolved_params << resolve_param(param['name'])
66
+ spec['type']
67
+ elsif inferred_pointer_wrapper
68
+ 'pointer'
69
+ else
70
+ 'struct'
55
71
  end
56
-
57
- "#{spec['name']}( #{resolved_params.join ', '} )"
58
- end
59
-
60
- def self.normalize_spec_hash(spec)
61
- normalized_spec = spec.dup
62
- normalized_spec.default = []
63
-
64
- normalized_spec['equivalent-struct']['members'] ||= []
65
- normalized_spec['equivalent-struct']['includes'] ||= []
66
-
67
- normalized_spec
68
72
  end
69
73
 
70
- private
71
-
72
- def header_guard
73
- "__#{@spec['name'].upcase}_HPP"
74
- end
74
+ # The underlying struct of this class.
75
+ attr_reader :struct
76
+
77
+ # Creates a class spec based on the provided hash spec.
78
+ #
79
+ # The scope can be provided if available.
80
+ #
81
+ # The hash must have the following keys:
82
+ # name:: the name of the class
83
+ # namespace:: the namespace to put the class into
84
+ # equivalent-struct:: a hash describing the struct this class wraps
85
+ #
86
+ # The following keys are optional:
87
+ # doc:: a string containing the documentation for this class
88
+ # constructors:: a list of function specs that can create this class
89
+ # destructor:: a function spec for the destructor of the class
90
+ # functions:: a list of function specs
91
+ # constants:: a list of constant specs
92
+ def initialize(spec, scope: Scope.new)
93
+ @spec = Marshal.load(Marshal.dump(spec))
94
+ TemplateSpec.replace_all_uses(@spec, *scope.templates)
95
+
96
+ @spec = ClassSpec.normalize_spec_hash(@spec)
97
+
98
+ @struct = if @spec.key?(EQUIVALENT_STRUCT_KEYWORD)
99
+ StructSpec.new(@spec[EQUIVALENT_STRUCT_KEYWORD])
100
+ end
101
+
102
+ @functions = @spec['constructors'].map do |constructor_spec|
103
+ full_spec = constructor_spec.dup
104
+ full_spec['name'] = @spec['name']
105
+ full_spec['params'] = constructor_spec['wrapped-function']['params']
106
+
107
+ FunctionSpec.new(full_spec, self, constructor: true)
108
+ end
75
109
 
76
- def declaration_includes
77
- includes = @spec['equivalent-struct']['includes'].dup
110
+ if @spec.key?('destructor')
111
+ destructor_spec = @spec['destructor'].dup
112
+ destructor_spec['name'] = "~#{@spec['name']}"
78
113
 
79
- @functions.each do |func|
80
- includes.concat func.declaration_includes
114
+ @functions << FunctionSpec.new(destructor_spec, self, destructor: true)
81
115
  end
82
116
 
83
- @constants.each do |const|
84
- includes.concat const.declaration_includes
117
+ @spec['functions'].each do |function_spec|
118
+ @functions << FunctionSpec.new(function_spec, self)
85
119
  end
86
120
 
87
- includes.uniq
88
- end
89
-
90
- def definition_includes
91
- includes = ["#{@spec['name']}.hpp"]
92
-
93
- @functions.each do |func|
94
- includes.concat func.definition_includes
121
+ @constants = @spec['constants'].map do |constant_spec|
122
+ ConstantSpec.new(constant_spec)
95
123
  end
96
124
 
97
- @constants.each do |const|
98
- includes.concat const.definition_includes
99
- end
125
+ @doc = @spec.key?('doc') ? Comment.new(@spec['doc']) : nil
100
126
 
101
- includes.uniq
127
+ scope << self
128
+ @scope = scope
102
129
  end
103
130
 
104
- def pointer_wrapper?
105
- @spec['constructors'].each do |constructor_spec|
106
- return_type = constructor_spec['wrapped-function']['return']['type']
107
-
108
- return true if return_type == 'equivalent-struct-pointer'
109
- end
131
+ # Returns a cast of an instance of this class with the provided name to the
132
+ # specified type. Optionally the from parameter may hold the type of the
133
+ # instance, either a reference or a pointer.
134
+ def cast(instance, to, from = name)
135
+ member_access = from.pointer? ? '->' : '.'
110
136
 
111
- false
112
- end
137
+ struct = "struct #{@struct.name}"
113
138
 
114
- def equivalent_member(member)
115
- if pointer_wrapper?
116
- "this->equivalent->#{member}"
117
- else
118
- "this->equivalent.#{member}"
139
+ if [EQUIVALENT_STRUCT_KEYWORD, struct].include?(to)
140
+ "#{'*' if pointer_wrapper?}#{instance}#{member_access}equivalent"
141
+ elsif [EQUIVALENT_POINTER_KEYWORD, "#{struct} *"].include?(to)
142
+ "#{'&' unless pointer_wrapper?}#{instance}#{member_access}equivalent"
119
143
  end
120
144
  end
121
145
 
122
- def equivalent_name
123
- if pointer_wrapper?
124
- '*equivalent'
125
- else
126
- 'equivalent'
127
- end
146
+ # Generates the wrapper class declaration and definition files.
147
+ def generate_wrappers
148
+ [generate_declaration_file, generate_definition_file]
128
149
  end
129
150
 
130
- def equivalent_struct
131
- if pointer_wrapper?
132
- '*(this->equivalent)'
133
- else
134
- 'this->equivalent'
135
- end
151
+ # The name of the class
152
+ def name
153
+ @spec['name']
136
154
  end
137
155
 
138
- def equivalent_struct_pointer
139
- if pointer_wrapper?
140
- 'this->equivalent'
141
- else
142
- '&this->equivalent'
143
- end
156
+ # True if this class overloads the given one. A class is considered an
157
+ # overload if its parent is the given class, it has the same equivalent
158
+ # struct name, and the equivalent struct has a set of rules. The overloaded
159
+ # class cannot have any rules in its equivalent struct, or it will not be
160
+ # overloaded.
161
+ def overloads?(parent_spec)
162
+ return false unless parent_spec.struct&.rules&.empty?
163
+
164
+ parent_spec.struct.name == struct_name &&
165
+ parent_spec.name == parent_name &&
166
+ !@struct.rules.empty?
144
167
  end
145
168
 
146
- def wrapped_constructor_signature(index)
147
- function_spec = @spec['constructors'][index]['wrapped-function']
148
-
149
- "#{@spec['name']}( #{FunctionSpec.param_list function_spec} )"
169
+ # The name of the parent of this class, or nil if there is no parent.
170
+ def parent_name
171
+ @spec['parent']['name'] if child?
150
172
  end
151
173
 
152
- def destructor_signature
153
- "~#{@spec['name']}( void )"
174
+ # Determines if this class is a wrapper for a struct pointer or not.
175
+ def pointer_wrapper?
176
+ @spec['type'] == 'pointer'
154
177
  end
155
178
 
156
- def wrapped_constructor_definition(index)
157
- constructor_spec = @spec['constructors'][index]
158
- wrapped_function = constructor_spec['wrapped-function']
159
-
160
- yield "#{@spec['name']}::#{wrapped_constructor_signature(index)}{"
161
-
162
- result = resolve_param wrapped_function['return']['type']
163
-
164
- yield " #{result} = #{function_call wrapped_function};"
165
-
166
- yield '}'
179
+ # The name of the equivalent struct of this class.
180
+ def struct_name
181
+ @struct.name
167
182
  end
168
183
 
169
- def member_constructor_signature
170
- params = []
171
-
172
- @spec['equivalent-struct']['members'].each do |member|
173
- params << ClassSpec.typed_variable(member['type'], member['name'])
184
+ # Gives a code snippet that accesses the equivalent struct from within the
185
+ # class using the 'this' keyword.
186
+ def this_struct
187
+ if pointer_wrapper?
188
+ '*(this->equivalent)'
189
+ else
190
+ 'this->equivalent'
174
191
  end
175
-
176
- "#{@spec['name']}( #{params.join ', '} )"
177
192
  end
178
193
 
179
- def struct_constructor_signature
180
- struct_name = @spec['equivalent-struct']['name']
181
- "#{@spec['name']}( struct #{struct_name} equivalent )"
194
+ # Gives a code snippet that accesses the equivalent struct pointer from
195
+ # within the class using the 'this' keyword.
196
+ def this_struct_pointer
197
+ "#{'&' unless pointer_wrapper?}this->equivalent"
182
198
  end
183
199
 
184
- def pointer_constructor_signature
185
- struct_name = @spec['equivalent-struct']['name']
186
- "#{@spec['name']}( struct #{struct_name} *equivalent )"
200
+ # Returns the ClassSpec for the given type in this class's scope.
201
+ def type(type)
202
+ @scope.type(type)
187
203
  end
188
204
 
189
- def generate_declaration_file
190
- filename = "#{@spec['name']}.hpp"
205
+ # Returns true if the given type exists in this class's scope.
206
+ def type?(type)
207
+ @scope.type?(type)
208
+ end
191
209
 
192
- File.open(filename, 'w') do |file|
193
- declaration_contents do |line|
194
- file.puts line
195
- end
196
- end
210
+ private
197
211
 
198
- filename
212
+ # True if the class has a parent.
213
+ def child?
214
+ @spec.key?('parent')
199
215
  end
200
216
 
217
+ # Gives the content of the class declaration to a block, line by line.
201
218
  def declaration_contents
202
219
  yield "#ifndef #{header_guard}"
203
220
  yield "#define #{header_guard}"
@@ -210,36 +227,38 @@ module Wrapture
210
227
  yield
211
228
  yield "namespace #{@spec['namespace']} {"
212
229
  yield
213
- yield " class #{@spec['name']} {"
230
+
231
+ documentation { |line| yield " #{line}" }
232
+ parent = if child?
233
+ ": public #{parent_name} "
234
+ else
235
+ ''
236
+ end
237
+ yield " class #{@spec['name']} #{parent}{"
238
+
214
239
  yield ' public:'
215
240
 
216
241
  yield unless @constants.empty?
217
242
  @constants.each do |const|
218
- yield " #{const.declaration};"
243
+ const.declaration { |line| yield " #{line}" }
219
244
  end
220
245
 
221
246
  yield
222
- struct_name = @spec['equivalent-struct']['name']
223
- yield " struct #{struct_name} #{equivalent_name};"
247
+ equivalent_member_declaration { |line| yield " #{line}" }
224
248
  yield
225
249
 
226
- unless @spec['equivalent-struct']['members'].empty?
227
- yield " #{member_constructor_signature};"
228
- end
250
+ member_constructor_declaration { |line| yield " #{line}" }
229
251
 
230
- unless pointer_wrapper?
231
- yield " #{struct_constructor_signature};"
232
- yield " #{pointer_constructor_signature};"
233
- end
252
+ pointer_constructor_declaration { |line| yield " #{line}" }
234
253
 
235
- @spec['constructors'].each_index do |constructor|
236
- yield " #{wrapped_constructor_signature constructor};"
254
+ unless !@struct || pointer_wrapper?
255
+ yield " #{struct_constructor_signature};"
237
256
  end
238
257
 
239
- yield " #{destructor_signature};" if @spec.key? 'destructor'
258
+ overload_declaration { |line| yield " #{line}" }
240
259
 
241
260
  @functions.each do |func|
242
- yield " #{func.declaration};"
261
+ func.declaration { |line| yield " #{line}" }
243
262
  end
244
263
 
245
264
  yield ' };' # end of class
@@ -249,18 +268,26 @@ module Wrapture
249
268
  yield '#endif' # end of header guard
250
269
  end
251
270
 
252
- def generate_definition_file
253
- filename = "#{@spec['name']}.cpp"
271
+ # A list of includes needed for the declaration of the class.
272
+ def declaration_includes
273
+ includes = @spec['includes'].dup
254
274
 
255
- File.open(filename, 'w') do |file|
256
- definition_contents do |line|
257
- file.puts line
258
- end
275
+ includes.concat(@struct.includes) if @struct
276
+
277
+ @functions.each do |func|
278
+ includes.concat(func.declaration_includes)
259
279
  end
260
280
 
261
- filename
281
+ @constants.each do |const|
282
+ includes.concat(const.declaration_includes)
283
+ end
284
+
285
+ includes.concat(@spec['parent']['includes']) if child?
286
+
287
+ includes.uniq
262
288
  end
263
289
 
290
+ # Gives the content of the class definition to a block, line by line.
264
291
  def definition_contents
265
292
  definition_includes.each do |include_file|
266
293
  yield "#include <#{include_file}>"
@@ -271,68 +298,264 @@ module Wrapture
271
298
 
272
299
  yield unless @constants.empty?
273
300
  @constants.each do |const|
274
- yield " #{const.definition @spec['name']};"
301
+ yield " #{const.definition(@spec['name'])};"
275
302
  end
276
303
 
277
- unless @spec['equivalent-struct']['members'].empty?
278
- yield
279
- yield " #{@spec['name']}::#{member_constructor_signature} {"
280
-
281
- @spec['equivalent-struct']['members'].each do |member|
282
- member_decl = equivalent_member member['name']
283
- yield " #{member_decl} = #{member['name']};"
284
- end
304
+ member_constructor_definition { |line| yield " #{line}" }
285
305
 
286
- yield ' }'
287
- end
306
+ pointer_constructor_definition { |line| yield " #{line}" }
288
307
 
289
- unless pointer_wrapper?
308
+ unless pointer_wrapper? || !@struct
290
309
  yield
291
310
  yield " #{@spec['name']}::#{struct_constructor_signature} {"
292
311
 
293
- @spec['equivalent-struct']['members'].each do |member|
294
- member_decl = equivalent_member member['name']
312
+ @struct.members.each do |member|
313
+ member_decl = this_member(member['name'])
295
314
  yield " #{member_decl} = equivalent.#{member['name']};"
296
315
  end
297
316
 
298
317
  yield ' }'
318
+ end
299
319
 
320
+ overload_definition { |line| yield " #{line}" }
321
+
322
+ @functions.each do |func|
300
323
  yield
301
- yield " #{@spec['name']}::#{pointer_constructor_signature} {"
302
324
 
303
- @spec['equivalent-struct']['members'].each do |member|
304
- member_decl = equivalent_member member['name']
305
- yield " #{member_decl} = equivalent->#{member['name']};"
325
+ func.definition do |def_line|
326
+ yield " #{def_line}"
306
327
  end
328
+ end
307
329
 
308
- yield ' }'
330
+ yield
331
+ yield '}' # end of namespace
332
+ end
333
+
334
+ # A list of includes needed for the definition of the class.
335
+ def definition_includes
336
+ includes = ["#{@spec['name']}.hpp"]
337
+
338
+ includes.concat(@spec['includes'])
339
+
340
+ @functions.each do |func|
341
+ includes.concat(func.definition_includes)
309
342
  end
310
343
 
311
- @spec['constructors'].each_index do |constructor|
312
- yield
313
- wrapped_constructor_definition(constructor) do |line|
314
- yield " #{line}"
315
- end
344
+ @constants.each do |const|
345
+ includes.concat(const.definition_includes)
316
346
  end
317
347
 
318
- if @spec.key? 'destructor'
319
- yield
320
- yield " #{@spec['name']}::#{destructor_signature} {"
321
- func_spec = @spec['destructor']['wrapped-function']
322
- yield " #{function_call func_spec};"
323
- yield ' }'
348
+ includes.concat(overload_definition_includes)
349
+
350
+ includes.uniq
351
+ end
352
+
353
+ # Calls the given block for each line of the class documentation.
354
+ def documentation(&block)
355
+ @doc&.format_as_doxygen(max_line_length: 78) { |line| block.call(line) }
356
+ end
357
+
358
+ # Yields the declaration of the equivalent member if this class has one.
359
+ #
360
+ # A class might not have an equivalent member if it is able to use the
361
+ # parent class's, for example if the child class wraps the same struct.
362
+ def equivalent_member_declaration
363
+ return unless @struct
364
+
365
+ if child?
366
+ parent_spec = @scope.type(TypeSpec.new(parent_name))
367
+ member_reusable = !parent_spec.nil? &&
368
+ parent_spec.struct_name == @struct.name &&
369
+ parent_spec.pointer_wrapper? == pointer_wrapper?
370
+ return if member_reusable
324
371
  end
325
372
 
326
- @functions.each do |func|
327
- yield
373
+ yield "#{@struct.declaration(equivalent_name)};"
374
+ end
328
375
 
329
- func.definition(@spec['name']) do |def_line|
330
- yield " #{def_line}"
376
+ # Gives the name of the equivalent struct.
377
+ def equivalent_name
378
+ "#{'*' if pointer_wrapper?}equivalent"
379
+ end
380
+
381
+ # Generates the declaration of the class.
382
+ def generate_declaration_file
383
+ filename = "#{@spec['name']}.hpp"
384
+
385
+ File.open(filename, 'w') do |file|
386
+ declaration_contents do |line|
387
+ file.puts(line)
331
388
  end
332
389
  end
333
390
 
391
+ filename
392
+ end
393
+
394
+ # Generates the definition of the class.
395
+ def generate_definition_file
396
+ filename = "#{@spec['name']}.cpp"
397
+
398
+ File.open(filename, 'w') do |file|
399
+ definition_contents do |line|
400
+ file.puts(line)
401
+ end
402
+ end
403
+
404
+ filename
405
+ end
406
+
407
+ # The header guard for the class.
408
+ def header_guard
409
+ "__#{@spec['name'].upcase}_HPP"
410
+ end
411
+
412
+ # Yields the declaration of the member constructor for a class. This will be
413
+ # empty if the wrapped struct is a pointer wrapper.
414
+ def member_constructor_declaration
415
+ return unless @struct&.members?
416
+
417
+ yield "#{@spec['name']}( #{@struct.member_list_with_defaults} );"
418
+ end
419
+
420
+ # Yields the definition of the member constructor for a class. This will be
421
+ # empty if the wrapped struct is a pointer wrapper.
422
+ def member_constructor_definition
423
+ return unless @struct&.members?
424
+
425
+ yield "#{@spec['name']}::#{@spec['name']}( #{@struct.member_list} ) {"
426
+
427
+ @struct.members.each do |member|
428
+ member_decl = this_member(member['name'])
429
+ yield " #{member_decl} = #{member['name']};"
430
+ end
431
+
432
+ yield '}'
433
+ end
434
+
435
+ # Yields the declaration of the overload function for this class. If there
436
+ # is no overload function for this class, then nothing is yielded.
437
+ def overload_declaration
438
+ return unless @scope.overloads?(self)
439
+
440
+ yield "static #{name} *new#{name}( struct #{@struct.name} *equivalent );"
441
+ end
442
+
443
+ # Yields each line of the definition of the overload function, with a
444
+ # leading empty yield. If there is no overload function for this class,
445
+ # then nothing is yielded.
446
+ def overload_definition
447
+ return unless @scope.overloads?(self)
448
+
334
449
  yield
335
- yield '}' # end of namespace
450
+
451
+ parameter = "struct #{@struct.name} *equivalent"
452
+ yield "#{name} *#{name}::new#{name}( #{parameter} ) {"
453
+
454
+ line_prefix = ' '
455
+ @scope.overloads(self).each do |overload|
456
+ check = overload.struct.rules_check('equivalent')
457
+ yield "#{line_prefix}if( #{check} ) {"
458
+ yield " return new #{overload.name}( equivalent );"
459
+ line_prefix = ' } else '
460
+ end
461
+
462
+ yield "#{line_prefix}{"
463
+ yield " return new #{name}( equivalent );"
464
+ yield ' }'
465
+ yield '}'
466
+ end
467
+
468
+ # A list of the includes needed for the overload definitions.
469
+ def overload_definition_includes
470
+ @scope.overloads(self).map { |overload| "#{overload.name}.hpp" }
471
+ end
472
+
473
+ # The initializer for the pointer constructor, if one is available, or an
474
+ # empty string if not.
475
+ def parent_provides_initializer?
476
+ return false if !pointer_wrapper? || !child?
477
+
478
+ parent_spec = @scope.type(TypeSpec.new(parent_name))
479
+
480
+ !parent_spec.nil? &&
481
+ parent_spec.pointer_wrapper? &&
482
+ parent_spec.struct_name == @struct.name
483
+ end
484
+
485
+ # Yields the declaration of the pointer constructor for a class.
486
+ #
487
+ # If this class does not have an equivalent struct, or if there is already
488
+ # a constructor defined with this signature, then this function will return
489
+ # with no output.
490
+ def pointer_constructor_declaration
491
+ return unless @struct
492
+
493
+ signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
494
+ return if @functions.any? do |func|
495
+ func.constructor? && func.signature.start_with?(signature_prefix)
496
+ end
497
+
498
+ yield "#{pointer_constructor_signature};"
499
+ end
500
+
501
+ # Yields the definition of the pointer constructor for a class.
502
+ #
503
+ # If this class has no equivalent struct, or if there is already a
504
+ # constructor provided with this signature, then this function will return
505
+ # with no output.
506
+ #
507
+ # If this is a pointer wrapper class, then the constructor will simply set
508
+ # the underlying pointer to the provided one, and return the new object.
509
+ #
510
+ # If this is a struct wrapper class, then a constructor will be created that
511
+ # sets each member of the wrapped struct to the provided value.
512
+ def pointer_constructor_definition
513
+ return unless @struct
514
+
515
+ signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
516
+ return if @functions.any? do |func|
517
+ func.constructor? && func.signature.start_with?(signature_prefix)
518
+ end
519
+
520
+ initializer = pointer_constructor_initializer
521
+ yield "#{@spec['name']}::#{pointer_constructor_signature} #{initializer}{"
522
+
523
+ if pointer_wrapper?
524
+ yield ' this->equivalent = equivalent;'
525
+ else
526
+ @struct.members.each do |member|
527
+ member_decl = this_member(member['name'])
528
+ yield " #{member_decl} = equivalent->#{member['name']};"
529
+ end
530
+ end
531
+
532
+ yield '}'
533
+ end
534
+
535
+ # The initializer for the pointer constructor, if one is available, or an
536
+ # empty string if not.
537
+ def pointer_constructor_initializer
538
+ if parent_provides_initializer?
539
+ ": #{parent_name}( equivalent ) "
540
+ else
541
+ ''
542
+ end
543
+ end
544
+
545
+ # The signature of the constructor given an equivalent strucct pointer.
546
+ def pointer_constructor_signature
547
+ "#{@spec['name']}( #{@struct.pointer_declaration 'equivalent'} )"
548
+ end
549
+
550
+ # The signature of the constructor given an equivalent struct type.
551
+ def struct_constructor_signature
552
+ "#{@spec['name']}( #{@struct.declaration 'equivalent'} )"
553
+ end
554
+
555
+ # Gives a code snippet that accesses a member of the equivalent struct for
556
+ # this class within the class using the 'this' keyword.
557
+ def this_member(member)
558
+ "this->equivalent#{pointer_wrapper? ? '->' : '.'}#{member}"
336
559
  end
337
560
  end
338
561
  end