wrapture 0.3.0 → 0.4.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: fb5e341af7e97543a1ee20d0a4d962f461372c5ef948de704c1508e346ca71c7
4
- data.tar.gz: af4cf7f89111e459a866107ebc46823b81d6a4de0bbdd944a3b5294dc0ad9220
3
+ metadata.gz: 0a808f785d4a2a78afea58ed889d1b177f73dbd820206bd1d0ff55f0a9de1d16
4
+ data.tar.gz: 91197fff6cf5343c365b8f4ad3a2efdc913576355bc90a82c5040fa28442058b
5
5
  SHA512:
6
- metadata.gz: 9a3e8e31db51f492ab179bc047de225cd7db2ceb7156c1734ce45929cbce325f38abd40d54637c87839d0b0cc2bbf41ec8cf491d128e0a61a01aaafa724c6ef3
7
- data.tar.gz: eb13f2975a79f0caf669b3a72408e9af1c61155598ce516c43d406e2b95f3016550724e2efe35766da6ebec0695d8052c2fa9b2149cedad87d0c89eab7672e44
6
+ metadata.gz: 561bdb4547a1e4617bba11c99921b2b475e4e6178e58202b066f51e2e6fee89ce11b64408d33e73952869dcef1ac1e2c793295f1bb79ac9bdcbee0e6b9e4eb49
7
+ data.tar.gz: ed3a3e6b8a2062bc0938cd18253f9f90f64893a2cfc26b9b4fe0f043ca66e2806f75cc9acfc0054cf18e2a99f93cd1b6fd089561a91363f76bd4accc7cae22b5
@@ -1,7 +1,23 @@
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
 
@@ -10,8 +26,16 @@ scope = Wrapture::Scope.new
10
26
  ARGV.each do |spec_file|
11
27
  spec = YAML.load_file(spec_file)
12
28
 
13
- spec['classes'].each do |class_spec|
14
- Wrapture::ClassSpec.new(class_spec, scope: scope)
29
+ spec.fetch('templates', []).each do |temp_spec|
30
+ scope << Wrapture::TemplateSpec.new(temp_spec)
31
+ end
32
+
33
+ spec.fetch('classes', []).each do |class_spec|
34
+ scope.add_class_spec_hash(class_spec)
35
+ end
36
+
37
+ spec.fetch('enums', []).each do |enum_spec|
38
+ scope.add_enum_spec_hash(enum_spec)
15
39
  end
16
40
  end
17
41
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # frozen_string_literal: true
4
4
 
5
- # Copyright 2019 Joel E. Anderson
5
+ # Copyright 2019-2020 Joel E. Anderson
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
8
8
  # you may not use this file except in compliance with the License.
@@ -19,15 +19,20 @@
19
19
  # Classes and functions for generating language wrappers
20
20
  module Wrapture
21
21
  require 'wrapture/action_spec'
22
+ require 'wrapture/comment'
22
23
  require 'wrapture/constant_spec'
23
24
  require 'wrapture/constants'
24
25
  require 'wrapture/class_spec'
26
+ require 'wrapture/enum_spec'
25
27
  require 'wrapture/errors'
26
28
  require 'wrapture/function_spec'
27
29
  require 'wrapture/normalize'
28
30
  require 'wrapture/rule_spec'
31
+ require 'wrapture/param_spec'
29
32
  require 'wrapture/scope'
30
33
  require 'wrapture/struct_spec'
34
+ require 'wrapture/template_spec'
35
+ require 'wrapture/type_spec'
31
36
  require 'wrapture/version'
32
37
  require 'wrapture/wrapped_function_spec'
33
38
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  # frozen_string_literal: true
4
4
 
5
+ #--
5
6
  # Copyright 2020 Joel E. Anderson
6
7
  #
7
8
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +16,7 @@
15
16
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
17
  # See the License for the specific language governing permissions and
17
18
  # limitations under the License.
18
-
19
- require 'wrapture/constants'
20
- require 'wrapture/errors'
19
+ #++
21
20
 
22
21
  module Wrapture
23
22
  # An action to take within a generated program.
@@ -2,6 +2,7 @@
2
2
 
3
3
  # frozen_string_literal: true
4
4
 
5
+ #--
5
6
  # Copyright 2019-2020 Joel E. Anderson
6
7
  #
7
8
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,11 +16,7 @@
15
16
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
17
  # See the License for the specific language governing permissions and
17
18
  # limitations under the License.
18
-
19
- require 'wrapture/constant_spec'
20
- require 'wrapture/constants'
21
- require 'wrapture/function_spec'
22
- require 'wrapture/normalize'
19
+ #++
23
20
 
24
21
  module Wrapture
25
22
  # A description of a class, including its constants, functions, and other
@@ -34,6 +31,9 @@ module Wrapture
34
31
  # it uses an unsupported version type, then an exception is raised.
35
32
  def self.normalize_spec_hash(spec)
36
33
  raise NoNamespace unless spec.key?('namespace')
34
+ raise MissingSpecKey, 'name key is required' unless spec.key?('name')
35
+
36
+ Comment.validate_doc(spec['doc']) if spec.key?('doc')
37
37
 
38
38
  normalized = spec.dup
39
39
  normalized.default = []
@@ -71,11 +71,6 @@ module Wrapture
71
71
  end
72
72
  end
73
73
 
74
- # Returns a string of the variable with it's type, properly formatted.
75
- def self.typed_variable(type, name)
76
- "#{type}#{' ' unless type.end_with?('*')}#{name}"
77
- end
78
-
79
74
  # The underlying struct of this class.
80
75
  attr_reader :struct
81
76
 
@@ -89,14 +84,20 @@ module Wrapture
89
84
  # equivalent-struct:: a hash describing the struct this class wraps
90
85
  #
91
86
  # The following keys are optional:
87
+ # doc:: a string containing the documentation for this class
92
88
  # constructors:: a list of function specs that can create this class
93
89
  # destructor:: a function spec for the destructor of the class
94
90
  # functions:: a list of function specs
95
91
  # constants:: a list of constant specs
96
92
  def initialize(spec, scope: Scope.new)
97
- @spec = ClassSpec.normalize_spec_hash(spec)
93
+ @spec = Marshal.load(Marshal.dump(spec))
94
+ TemplateSpec.replace_all_uses(@spec, *scope.templates)
95
+
96
+ @spec = ClassSpec.normalize_spec_hash(@spec)
98
97
 
99
- @struct = StructSpec.new @spec[EQUIVALENT_STRUCT_KEYWORD]
98
+ @struct = if @spec.key?(EQUIVALENT_STRUCT_KEYWORD)
99
+ StructSpec.new(@spec[EQUIVALENT_STRUCT_KEYWORD])
100
+ end
100
101
 
101
102
  @functions = @spec['constructors'].map do |constructor_spec|
102
103
  full_spec = constructor_spec.dup
@@ -121,32 +122,27 @@ module Wrapture
121
122
  ConstantSpec.new(constant_spec)
122
123
  end
123
124
 
125
+ @doc = @spec.key?('doc') ? Comment.new(@spec['doc']) : nil
126
+
124
127
  scope << self
125
128
  @scope = scope
126
129
  end
127
130
 
128
- # Returns a cast of an instance of this class to the provided type, if
129
- # possible.
130
- def cast_to(name, type)
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? ? '->' : '.'
136
+
131
137
  struct = "struct #{@struct.name}"
132
138
 
133
- if [EQUIVALENT_STRUCT_KEYWORD, struct].include?(type)
134
- equivalent_struct(name)
135
- elsif [EQUIVALENT_POINTER_KEYWORD, "#{struct} *"].include?(type)
136
- equivalent_struct_pointer(name)
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"
137
143
  end
138
144
  end
139
145
 
140
- # The equivalent struct of this class from an instance of it.
141
- def equivalent_struct(instance_name)
142
- "#{'*' if pointer_wrapper?}#{instance_name}.equivalent"
143
- end
144
-
145
- # A pointer to the equivalent struct of this class from an instance of it.
146
- def equivalent_struct_pointer(instance_name)
147
- "#{'&' unless pointer_wrapper?}#{instance_name}.equivalent"
148
- end
149
-
150
146
  # Generates the wrapper class declaration and definition files.
151
147
  def generate_wrappers
152
148
  [generate_declaration_file, generate_definition_file]
@@ -163,7 +159,7 @@ module Wrapture
163
159
  # class cannot have any rules in its equivalent struct, or it will not be
164
160
  # overloaded.
165
161
  def overloads?(parent_spec)
166
- return false unless parent_spec.struct.rules.empty?
162
+ return false unless parent_spec.struct&.rules&.empty?
167
163
 
168
164
  parent_spec.struct.name == struct_name &&
169
165
  parent_spec.name == parent_name &&
@@ -172,7 +168,12 @@ module Wrapture
172
168
 
173
169
  # The name of the parent of this class, or nil if there is no parent.
174
170
  def parent_name
175
- @spec['parent']['name'] if @spec.key?('parent')
171
+ @spec['parent']['name'] if child?
172
+ end
173
+
174
+ # Determines if this class is a wrapper for a struct pointer or not.
175
+ def pointer_wrapper?
176
+ @spec['type'] == 'pointer'
176
177
  end
177
178
 
178
179
  # The name of the equivalent struct of this class.
@@ -208,6 +209,11 @@ module Wrapture
208
209
 
209
210
  private
210
211
 
212
+ # True if the class has a parent.
213
+ def child?
214
+ @spec.key?('parent')
215
+ end
216
+
211
217
  # Gives the content of the class declaration to a block, line by line.
212
218
  def declaration_contents
213
219
  yield "#ifndef #{header_guard}"
@@ -222,7 +228,8 @@ module Wrapture
222
228
  yield "namespace #{@spec['namespace']} {"
223
229
  yield
224
230
 
225
- parent = if @spec.key?('parent')
231
+ documentation { |line| yield " #{line}" }
232
+ parent = if child?
226
233
  ": public #{parent_name} "
227
234
  else
228
235
  ''
@@ -233,23 +240,25 @@ module Wrapture
233
240
 
234
241
  yield unless @constants.empty?
235
242
  @constants.each do |const|
236
- yield " #{const.declaration};"
243
+ const.declaration { |line| yield " #{line}" }
237
244
  end
238
245
 
239
246
  yield
240
- yield " #{@struct.declaration equivalent_name};"
247
+ equivalent_member_declaration { |line| yield " #{line}" }
241
248
  yield
242
249
 
243
250
  member_constructor_declaration { |line| yield " #{line}" }
244
251
 
245
252
  pointer_constructor_declaration { |line| yield " #{line}" }
246
253
 
247
- yield " #{struct_constructor_signature};" unless pointer_wrapper?
254
+ unless !@struct || pointer_wrapper?
255
+ yield " #{struct_constructor_signature};"
256
+ end
248
257
 
249
258
  overload_declaration { |line| yield " #{line}" }
250
259
 
251
260
  @functions.each do |func|
252
- yield " #{func.declaration};"
261
+ func.declaration { |line| yield " #{line}" }
253
262
  end
254
263
 
255
264
  yield ' };' # end of class
@@ -263,7 +272,7 @@ module Wrapture
263
272
  def declaration_includes
264
273
  includes = @spec['includes'].dup
265
274
 
266
- includes.concat(@struct.includes)
275
+ includes.concat(@struct.includes) if @struct
267
276
 
268
277
  @functions.each do |func|
269
278
  includes.concat(func.declaration_includes)
@@ -273,7 +282,7 @@ module Wrapture
273
282
  includes.concat(const.declaration_includes)
274
283
  end
275
284
 
276
- includes.concat(@spec['parent']['includes']) if @spec.key?('parent')
285
+ includes.concat(@spec['parent']['includes']) if child?
277
286
 
278
287
  includes.uniq
279
288
  end
@@ -296,7 +305,7 @@ module Wrapture
296
305
 
297
306
  pointer_constructor_definition { |line| yield " #{line}" }
298
307
 
299
- unless pointer_wrapper?
308
+ unless pointer_wrapper? || !@struct
300
309
  yield
301
310
  yield " #{@spec['name']}::#{struct_constructor_signature} {"
302
311
 
@@ -313,7 +322,7 @@ module Wrapture
313
322
  @functions.each do |func|
314
323
  yield
315
324
 
316
- func.definition(@spec['name']) do |def_line|
325
+ func.definition do |def_line|
317
326
  yield " #{def_line}"
318
327
  end
319
328
  end
@@ -341,6 +350,29 @@ module Wrapture
341
350
  includes.uniq
342
351
  end
343
352
 
353
+ # Yields the class documentation one line at a time.
354
+ def documentation
355
+ @doc&.format_as_doxygen(max_line_length: 78) { |line| yield 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
371
+ end
372
+
373
+ yield "#{@struct.declaration(equivalent_name)};"
374
+ end
375
+
344
376
  # Gives the name of the equivalent struct.
345
377
  def equivalent_name
346
378
  "#{'*' if pointer_wrapper?}equivalent"
@@ -380,7 +412,7 @@ module Wrapture
380
412
  # Yields the declaration of the member constructor for a class. This will be
381
413
  # empty if the wrapped struct is a pointer wrapper.
382
414
  def member_constructor_declaration
383
- return unless @struct.members?
415
+ return unless @struct&.members?
384
416
 
385
417
  yield "#{@spec['name']}( #{@struct.member_list_with_defaults} );"
386
418
  end
@@ -388,7 +420,7 @@ module Wrapture
388
420
  # Yields the definition of the member constructor for a class. This will be
389
421
  # empty if the wrapped struct is a pointer wrapper.
390
422
  def member_constructor_definition
391
- return unless @struct.members?
423
+ return unless @struct&.members?
392
424
 
393
425
  yield "#{@spec['name']}::#{@spec['name']}( #{@struct.member_list} ) {"
394
426
 
@@ -440,9 +472,12 @@ module Wrapture
440
472
 
441
473
  # Yields the declaration of the pointer constructor for a class.
442
474
  #
443
- # If there is already a constructor provided with this signature, then this
444
- # function will return with no output.
475
+ # If this class does not have an equivalent struct, or if there is already
476
+ # a constructor defined with this signature, then this function will return
477
+ # with no output.
445
478
  def pointer_constructor_declaration
479
+ return unless @struct
480
+
446
481
  signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
447
482
  return if @functions.any? do |func|
448
483
  func.constructor? && func.signature.start_with?(signature_prefix)
@@ -453,21 +488,25 @@ module Wrapture
453
488
 
454
489
  # Yields the definition of the pointer constructor for a class.
455
490
  #
456
- # If there is already a constructor provided with this signature, then this
457
- # function will return with no output.
491
+ # If this class has no equivalent struct, or if there is already a
492
+ # constructor provided with this signature, then this function will return
493
+ # with no output.
458
494
  #
459
495
  # If this is a pointer wrapper class, then the constructor will simply set
460
- # the underlying pointer to the provied one, and return the new object.
496
+ # the underlying pointer to the provided one, and return the new object.
461
497
  #
462
498
  # If this is a struct wrapper class, then a constructor will be created that
463
499
  # sets each member of the wrapped struct to the provided value.
464
500
  def pointer_constructor_definition
501
+ return unless @struct
502
+
465
503
  signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
466
504
  return if @functions.any? do |func|
467
505
  func.constructor? && func.signature.start_with?(signature_prefix)
468
506
  end
469
507
 
470
- yield "#{@spec['name']}::#{pointer_constructor_signature} {"
508
+ initializer = pointer_constructor_initializer
509
+ yield "#{@spec['name']}::#{pointer_constructor_signature} #{initializer}{"
471
510
 
472
511
  if pointer_wrapper?
473
512
  yield ' this->equivalent = equivalent;'
@@ -481,16 +520,25 @@ module Wrapture
481
520
  yield '}'
482
521
  end
483
522
 
523
+ # The initializer for the pointer constructor, if one is available, or an
524
+ # empty string if not.
525
+ def pointer_constructor_initializer
526
+ if pointer_wrapper? && child?
527
+ parent_spec = @scope.type(TypeSpec.new(parent_name))
528
+ parent_usable = !parent_spec.nil? &&
529
+ parent_spec.pointer_wrapper? &&
530
+ parent_spec.struct_name == @struct.name
531
+ return ": #{parent_name}( equivalent ) " if parent_usable
532
+ end
533
+
534
+ ''
535
+ end
536
+
484
537
  # The signature of the constructor given an equivalent strucct pointer.
485
538
  def pointer_constructor_signature
486
539
  "#{@spec['name']}( #{@struct.pointer_declaration 'equivalent'} )"
487
540
  end
488
541
 
489
- # Determines if this class is a wrapper for a struct pointer or not.
490
- def pointer_wrapper?
491
- @spec['type'] == 'pointer'
492
- end
493
-
494
542
  # The signature of the constructor given an equivalent struct type.
495
543
  def struct_constructor_signature
496
544
  "#{@spec['name']}( #{@struct.declaration 'equivalent'} )"
@@ -0,0 +1,106 @@
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
+ # A comment that can be inserted in generated source code.
23
+ #
24
+ # Comments are primarily used to insert documentation about generated code for
25
+ # documentation generation tools such as Doxygen.
26
+ class Comment
27
+ # Validates a doc string.
28
+ def self.validate_doc(doc)
29
+ raise InvalidDoc, 'a doc must be a string' unless doc.is_a?(String)
30
+ end
31
+
32
+ # The raw text of the comment.
33
+ attr_reader :text
34
+
35
+ # Creates a comment from a string. If the provided string is nil, then an
36
+ # empty string is used.
37
+ def initialize(comment = '')
38
+ @text = comment.nil? ? '' : comment
39
+ end
40
+
41
+ # True if this comment is empty, false otherwise.
42
+ def empty?
43
+ @text.empty?
44
+ end
45
+
46
+ # Yields each line of the comment formatted as specified.
47
+ def format(line_prefix: '// ', first_line: nil, last_line: nil,
48
+ max_line_length: 80)
49
+ return if @text.empty?
50
+
51
+ yield first_line if first_line
52
+
53
+ paragraphs(max_line_length - line_prefix.length) do |line|
54
+ yield "#{line_prefix}#{line}".rstrip
55
+ end
56
+
57
+ yield last_line if last_line
58
+ end
59
+
60
+ # Yields each line of the comment formatted using Doxygen style.
61
+ def format_as_doxygen(max_line_length: 80)
62
+ format(line_prefix: ' * ', first_line: '/**',
63
+ last_line: ' */', max_line_length: max_line_length) do |line|
64
+ yield line
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # Yields the comment converted into paragraph-style blocks.
71
+ #
72
+ # Consecutive lines with text are concatenated together to the maximum line
73
+ # length, regardless of the original line length in the comment. One or more
74
+ # empty lines are written as a single empty line, separating paragraphs.
75
+ #
76
+ # Yielded lines may have trailing spaces, which are not considered part of
77
+ # the maximum length. The caller must strip these off.
78
+ def paragraphs(line_length)
79
+ running_line = String.new
80
+ newline_mode = true
81
+ @text.each_line do |line|
82
+ if line.strip.empty?
83
+ unless newline_mode
84
+ yield running_line
85
+ yield ''
86
+ running_line.clear
87
+ newline_mode = true
88
+ end
89
+ else
90
+ newline_mode = false
91
+ end
92
+
93
+ line.scan(/\S+/) do |word|
94
+ if running_line.length + word.length > line_length
95
+ yield running_line
96
+ running_line = word + ' '
97
+ else
98
+ running_line << word << ' '
99
+ end
100
+ end
101
+ end
102
+
103
+ yield running_line
104
+ end
105
+ end
106
+ end