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 +4 -4
- data/bin/wrapture +37 -4
- data/lib/wrapture.rb +29 -1
- data/lib/wrapture/action_spec.rb +80 -0
- data/lib/wrapture/class_spec.rb +421 -198
- data/lib/wrapture/comment.rb +107 -0
- data/lib/wrapture/constant_spec.rb +50 -8
- data/lib/wrapture/constants.rb +41 -0
- data/lib/wrapture/enum_spec.rb +155 -0
- data/lib/wrapture/errors.rb +67 -0
- data/lib/wrapture/function_spec.rb +350 -39
- data/lib/wrapture/normalize.rb +62 -0
- data/lib/wrapture/param_spec.rb +132 -0
- data/lib/wrapture/rule_spec.rb +118 -0
- data/lib/wrapture/scope.rb +112 -0
- data/lib/wrapture/struct_spec.rb +129 -0
- data/lib/wrapture/template_spec.rb +435 -0
- data/lib/wrapture/type_spec.rb +178 -0
- data/lib/wrapture/version.rb +29 -1
- data/lib/wrapture/wrapped_function_spec.rb +134 -0
- metadata +27 -7
@@ -1,74 +1,385 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
|
1
3
|
# frozen_string_literal: true
|
2
4
|
|
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
|
+
#++
|
20
|
+
|
3
21
|
module Wrapture
|
4
|
-
##
|
5
22
|
# A description of a function to be generated, including details about the
|
6
23
|
# underlying implementation.
|
7
24
|
class FunctionSpec
|
25
|
+
# Returns a copy of the return type specification +spec+.
|
26
|
+
def self.normalize_return_hash(spec)
|
27
|
+
if spec.nil?
|
28
|
+
{ 'type' => 'void', 'includes' => [] }
|
29
|
+
else
|
30
|
+
normalized = Marshal.load(Marshal.dump(spec))
|
31
|
+
Comment.validate_doc(spec['doc']) if spec.key?('doc')
|
32
|
+
normalized['type'] ||= 'void'
|
33
|
+
normalized['includes'] = Wrapture.normalize_includes(spec['includes'])
|
34
|
+
normalized
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Normalizes a hash specification of a function. Normalization will check
|
39
|
+
# for things like invalid keys, duplicate entries in include lists, and will
|
40
|
+
# set missing keys to their default values (for example, an empty list if no
|
41
|
+
# includes are given).
|
8
42
|
def self.normalize_spec_hash(spec)
|
9
|
-
|
43
|
+
Comment.validate_doc(spec['doc']) if spec.key?('doc')
|
44
|
+
|
45
|
+
normalized = spec.dup
|
46
|
+
|
47
|
+
normalized['version'] = Wrapture.spec_version(spec)
|
48
|
+
normalized['virtual'] = Wrapture.normalize_boolean(spec, 'virtual')
|
49
|
+
normalized['params'] = ParamSpec.normalize_param_list(spec['params'])
|
50
|
+
normalized['return'] = normalize_return_hash(spec['return'])
|
51
|
+
|
52
|
+
overload = Wrapture.normalize_boolean(normalized['return'], 'overloaded')
|
53
|
+
normalized['return']['overloaded'] = overload
|
54
|
+
|
55
|
+
normalized
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates a function spec based on the provided function spec.
|
59
|
+
#
|
60
|
+
# The hash must have the following keys:
|
61
|
+
# name:: the name of the function
|
62
|
+
# params:: a list of parameter specifications
|
63
|
+
# wrapped-function:: a hash describing the function to be wrapped
|
64
|
+
#
|
65
|
+
# Each parameter specification must have a 'name' key with the name of the
|
66
|
+
# parameter and a 'type' key with its type. The type key may be ommitted
|
67
|
+
# if the name of the parameter is '...' in which case the generated function
|
68
|
+
# will be made variadic. It may optionally have an 'includes' key with
|
69
|
+
# includes that are required (for example to support the type) and/or a
|
70
|
+
# 'doc' key with documentation of the parameter.
|
71
|
+
#
|
72
|
+
# Only one parameter named '...' is allowed in a specification. If more than
|
73
|
+
# one is provided, then only the first encountered will be used. This
|
74
|
+
# parameter should also be last - if it is not, it will be moved to the end
|
75
|
+
# of the parameter list during normalization.
|
76
|
+
#
|
77
|
+
# The wrapped-function must have a 'name' key with the name of the function,
|
78
|
+
# and a 'params' key with a list of parameters (each a hash with a 'name'
|
79
|
+
# and 'type' key). Optionally, it may also include an 'includes' key with a
|
80
|
+
# list of includes that are needed for this function to compile. The wrapped
|
81
|
+
# function may be left out entirely, but the function will not be definable
|
82
|
+
# if this is the case.
|
83
|
+
#
|
84
|
+
# The following keys are optional:
|
85
|
+
# doc:: a string containing the documentation for this function
|
86
|
+
# return:: a specification of the return value for this function
|
87
|
+
# static:: set to true if this is a static function
|
88
|
+
#
|
89
|
+
# The return specification may have either a 'type' key with the name of the
|
90
|
+
# type the function returns, and/or a 'doc' key with documentation on the
|
91
|
+
# return value itself. If neither of these is needed, then the return
|
92
|
+
# specification may simply be omitted.
|
93
|
+
#
|
94
|
+
# The 'type' key of the return spec may also be set to 'self-reference'
|
95
|
+
# which will have the function return a reference to the instance it was
|
96
|
+
# called on. Of course, this cannot be used from a function that is not a
|
97
|
+
# class method.
|
98
|
+
def initialize(spec, owner = Scope.new, constructor: false,
|
99
|
+
destructor: false)
|
100
|
+
@owner = owner
|
101
|
+
@spec = FunctionSpec.normalize_spec_hash(spec)
|
102
|
+
@wrapped = if @spec.key?('wrapped-function')
|
103
|
+
WrappedFunctionSpec.new(@spec['wrapped-function'])
|
104
|
+
end
|
105
|
+
@params = ParamSpec.new_list(@spec['params'])
|
106
|
+
@return_type = TypeSpec.new(@spec['return']['type'])
|
107
|
+
@constructor = constructor
|
108
|
+
@destructor = destructor
|
109
|
+
end
|
110
|
+
|
111
|
+
# A TypeSpec describing the return type of this function.
|
112
|
+
attr_reader :return_type
|
113
|
+
|
114
|
+
# True if the function is a constructor, false otherwise.
|
115
|
+
def constructor?
|
116
|
+
@constructor
|
117
|
+
end
|
118
|
+
|
119
|
+
# A list of includes needed for the declaration of the function.
|
120
|
+
def declaration_includes
|
121
|
+
includes = @spec['return']['includes'].dup
|
122
|
+
@params.each { |param| includes.concat(param.includes) }
|
123
|
+
includes.concat(@return_type.includes)
|
124
|
+
includes.uniq
|
125
|
+
end
|
126
|
+
|
127
|
+
# True if this function can be defined.
|
128
|
+
def definable?
|
129
|
+
definable_check
|
130
|
+
rescue UndefinableSpec
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
134
|
+
# A list of includes needed for the definition of the function.
|
135
|
+
def definition_includes
|
136
|
+
includes = @wrapped.includes
|
137
|
+
includes.concat(@spec['return']['includes'])
|
138
|
+
@params.each { |param| includes.concat(param.includes) }
|
139
|
+
includes.concat(@return_type.includes)
|
140
|
+
includes << 'stdarg.h' if variadic?
|
141
|
+
includes.uniq
|
142
|
+
end
|
143
|
+
|
144
|
+
# The name of the function.
|
145
|
+
def name
|
146
|
+
@spec['name']
|
147
|
+
end
|
148
|
+
|
149
|
+
# A string with the parameter list for this function.
|
150
|
+
def param_list
|
151
|
+
ParamSpec.signature(@params, self)
|
152
|
+
end
|
153
|
+
|
154
|
+
# The name of the function with the class name, if it exists.
|
155
|
+
def qualified_name
|
156
|
+
if @owner.is_a?(ClassSpec)
|
157
|
+
"#{@owner.name}::#{name}"
|
158
|
+
else
|
159
|
+
name
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Gives an expression for calling a given parameter within this function.
|
164
|
+
# Equivalent structs and pointers are resolved, as well as casts between
|
165
|
+
# types if they are known within the scope of this function.
|
166
|
+
def resolve_wrapped_param(param_spec)
|
167
|
+
used_param = @params.find { |p| p.name == param_spec['value'] }
|
168
|
+
|
169
|
+
if param_spec['value'] == EQUIVALENT_STRUCT_KEYWORD
|
170
|
+
@owner.this_struct
|
171
|
+
elsif param_spec['value'] == EQUIVALENT_POINTER_KEYWORD
|
172
|
+
@owner.this_struct_pointer
|
173
|
+
elsif param_spec['value'] == '...'
|
174
|
+
'variadic_args'
|
175
|
+
elsif castable?(param_spec)
|
176
|
+
param_class = @owner.type(used_param.type)
|
177
|
+
param_class.cast(used_param.name,
|
178
|
+
param_spec['type'],
|
179
|
+
used_param.type)
|
180
|
+
else
|
181
|
+
param_spec['value']
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Calls return_expression on the return type of this function. +func_name+
|
186
|
+
# is passed to return_expression if provided.
|
187
|
+
def return_expression(func_name: name)
|
188
|
+
if @constructor || @destructor
|
189
|
+
signature(func_name: func_name)
|
190
|
+
else
|
191
|
+
resolved_return.return_expression(self, func_name: func_name)
|
192
|
+
end
|
193
|
+
end
|
10
194
|
|
11
|
-
|
12
|
-
|
13
|
-
|
195
|
+
# The signature of the function. +func_name+ can be used to override the
|
196
|
+
# function name if needed, for example if a class name qualifier is needed.
|
197
|
+
def signature(func_name: name)
|
198
|
+
"#{func_name}( #{param_list} )"
|
199
|
+
end
|
200
|
+
|
201
|
+
# Calls the given block once for each line of the declaration of the
|
202
|
+
# function, including any documentation.
|
203
|
+
def declaration(&block)
|
204
|
+
doc.format_as_doxygen(max_line_length: 76) { |line| block.call(line) }
|
205
|
+
|
206
|
+
modifier_prefix = if @spec['static']
|
207
|
+
'static '
|
208
|
+
elsif virtual?
|
209
|
+
'virtual '
|
210
|
+
else
|
211
|
+
''
|
212
|
+
end
|
213
|
+
|
214
|
+
block.call("#{modifier_prefix}#{return_expression};")
|
215
|
+
end
|
216
|
+
|
217
|
+
# Gives the definition of the function in a block, line by line.
|
218
|
+
def definition
|
219
|
+
definable_check
|
14
220
|
|
15
|
-
|
16
|
-
|
17
|
-
|
221
|
+
yield "#{return_expression(func_name: qualified_name)} {"
|
222
|
+
|
223
|
+
locals { |declaration| yield " #{declaration}" }
|
224
|
+
|
225
|
+
yield " va_start( variadic_args, #{@params[-2].name} );" if variadic?
|
226
|
+
|
227
|
+
if @wrapped.error_check?
|
228
|
+
yield
|
229
|
+
yield " #{wrapped_call_expression};"
|
230
|
+
yield
|
231
|
+
@wrapped.error_check(return_val: return_variable) do |line|
|
232
|
+
yield " #{line}"
|
233
|
+
end
|
234
|
+
else
|
235
|
+
yield " #{wrapped_call_expression};"
|
18
236
|
end
|
19
237
|
|
20
|
-
|
238
|
+
yield ' va_end( variadic_args );' if variadic?
|
239
|
+
|
240
|
+
if @return_type.self?
|
241
|
+
yield ' return *this;'
|
242
|
+
elsif @spec['return']['type'] != 'void' && !returns_call_directly?
|
243
|
+
yield ' return return_val;'
|
244
|
+
end
|
21
245
|
|
22
|
-
|
246
|
+
yield '}'
|
23
247
|
end
|
24
248
|
|
25
|
-
|
26
|
-
|
249
|
+
# A Comment holding the function documentation.
|
250
|
+
def doc
|
251
|
+
comment = String.new
|
252
|
+
comment << @spec['doc'] if @spec.key?('doc')
|
27
253
|
|
28
|
-
params
|
254
|
+
@params
|
255
|
+
.reject { |param| param.doc.empty? }
|
256
|
+
.each { |param| comment << "\n\n" << param.doc.text }
|
29
257
|
|
30
|
-
spec['
|
31
|
-
|
258
|
+
if @spec['return'].key?('doc')
|
259
|
+
comment << "\n\n@return " << @spec['return']['doc']
|
32
260
|
end
|
33
261
|
|
34
|
-
|
262
|
+
Comment.new(comment)
|
35
263
|
end
|
36
264
|
|
37
|
-
|
38
|
-
|
39
|
-
|
265
|
+
# A resolved type, given a TypeSpec +type+. Resolved types will not have any
|
266
|
+
# keywords like +equivalent-struct+, which will be resolved to their
|
267
|
+
# effective type.
|
268
|
+
def resolve_type(type)
|
269
|
+
if type.equivalent_struct?
|
270
|
+
TypeSpec.new("struct #{@owner.struct_name}")
|
271
|
+
elsif type.equivalent_pointer?
|
272
|
+
TypeSpec.new("struct #{@owner.struct_name} *")
|
273
|
+
elsif type.self?
|
274
|
+
TypeSpec.new("#{@owner.name}&")
|
275
|
+
else
|
276
|
+
type
|
277
|
+
end
|
40
278
|
end
|
41
279
|
|
42
|
-
|
43
|
-
|
280
|
+
# True if the function is variadic.
|
281
|
+
def variadic?
|
282
|
+
@params.last&.variadic?
|
44
283
|
end
|
45
284
|
|
46
|
-
|
47
|
-
|
48
|
-
|
285
|
+
# True if the function is virtual.
|
286
|
+
def virtual?
|
287
|
+
@spec['virtual']
|
288
|
+
end
|
49
289
|
|
50
|
-
|
290
|
+
private
|
291
|
+
|
292
|
+
# True if the return value of the wrapped call needs to be captured in a
|
293
|
+
# local variable.
|
294
|
+
def capture_return?
|
295
|
+
!@constructor &&
|
296
|
+
@wrapped.use_return? || returns_return_val?
|
51
297
|
end
|
52
298
|
|
53
|
-
|
54
|
-
|
299
|
+
# True if the provided wrapped param spec can be cast to when used in this
|
300
|
+
# function.
|
301
|
+
def castable?(wrapped_param)
|
302
|
+
param = @params.find { |p| p.name == wrapped_param['value'] }
|
303
|
+
|
304
|
+
!param.nil? &&
|
305
|
+
!wrapped_param['type'].nil? &&
|
306
|
+
@owner.type?(param.type)
|
55
307
|
end
|
56
308
|
|
57
|
-
|
58
|
-
|
59
|
-
|
309
|
+
# Raises an exception if this function cannot be defined as is. Returns
|
310
|
+
# true otherwise.
|
311
|
+
def definable_check
|
312
|
+
if @wrapped.nil?
|
313
|
+
raise UndefinableSpec, 'no wrapped function was specified'
|
314
|
+
end
|
315
|
+
|
316
|
+
true
|
60
317
|
end
|
61
318
|
|
62
|
-
|
63
|
-
|
64
|
-
yield
|
319
|
+
# Yields a declaration of each local variable used by the function.
|
320
|
+
def locals
|
321
|
+
yield 'va_list variadic_args;' if variadic?
|
65
322
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
323
|
+
if capture_return?
|
324
|
+
wrapped_type = resolve_type(@wrapped.return_val_type)
|
325
|
+
yield "#{wrapped_type.variable('return_val')};"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# The resolved type of the return type.
|
330
|
+
def resolved_return
|
331
|
+
@return_type.resolve(self)
|
332
|
+
end
|
333
|
+
|
334
|
+
# The function to use to create the return value of the function.
|
335
|
+
def return_cast(value)
|
336
|
+
if @return_type == @wrapped.return_val_type
|
337
|
+
value
|
338
|
+
elsif @spec['return']['overloaded']
|
339
|
+
"new#{@spec['return']['type'].chomp('*').strip} ( #{value} )"
|
340
|
+
else
|
341
|
+
resolved_return.cast_expression(value)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# The name of the variable holding the return value.
|
346
|
+
def return_variable
|
347
|
+
if @constructor
|
348
|
+
'this->equivalent'
|
349
|
+
else
|
350
|
+
'return_val'
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# True if the function returns the result of the wrapped function call
|
355
|
+
# directly without any after actions.
|
356
|
+
def returns_call_directly?
|
357
|
+
!@constructor &&
|
358
|
+
!@destructor &&
|
359
|
+
!%w[void self-reference].include?(@spec['return']['type']) &&
|
360
|
+
!@wrapped.error_check?
|
361
|
+
end
|
362
|
+
|
363
|
+
# True if the function returns the return_val variable.
|
364
|
+
def returns_return_val?
|
365
|
+
!@return_type.self? &&
|
366
|
+
@spec['return']['type'] != 'void' &&
|
367
|
+
!returns_call_directly?
|
368
|
+
end
|
369
|
+
|
370
|
+
# The expression containing the call to the underlying wrapped function.
|
371
|
+
def wrapped_call_expression
|
372
|
+
call = @wrapped.call_from(self)
|
373
|
+
|
374
|
+
if @constructor
|
375
|
+
"this->equivalent = #{call}"
|
376
|
+
elsif @wrapped.error_check?
|
377
|
+
"return_val = #{call}"
|
378
|
+
elsif returns_call_directly?
|
379
|
+
"return #{return_cast(call)}"
|
380
|
+
else
|
381
|
+
call
|
382
|
+
end
|
72
383
|
end
|
73
384
|
end
|
74
385
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
#--
|
6
|
+
# Copyright 2019 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
|
+
# Normalizes a spec key to be boolean, raising an error if it is not. Keys
|
23
|
+
# that are not present are defaulted to false.
|
24
|
+
def self.normalize_boolean(spec, key)
|
25
|
+
is_boolean = [true, false].include?(spec[key])
|
26
|
+
error_msg = "'#{key}' key may only be true or false"
|
27
|
+
raise(InvalidSpecKey, error_msg) unless !spec.key?(key) || is_boolean
|
28
|
+
|
29
|
+
spec.key?(key) && spec[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Normalizes an include list for an element. A single string will be converted
|
33
|
+
# into an array containing the single string, and a nil will be converted to
|
34
|
+
# an empty array.
|
35
|
+
def self.normalize_includes(includes)
|
36
|
+
if includes.nil?
|
37
|
+
[]
|
38
|
+
elsif includes.is_a? String
|
39
|
+
[includes]
|
40
|
+
else
|
41
|
+
includes.uniq
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the spec version for the provided spec. If the version is not
|
46
|
+
# provided in the spec, the newest version that the spec is compliant with
|
47
|
+
# will be returned instead.
|
48
|
+
#
|
49
|
+
# If this spec uses a version unsupported by this version of Wrapture or the
|
50
|
+
# spec is otherwise invalid, an exception is raised.
|
51
|
+
def self.spec_version(spec)
|
52
|
+
if spec.key?('version') && !Wrapture.supports_version?(spec['version'])
|
53
|
+
raise UnsupportedSpecVersion
|
54
|
+
end
|
55
|
+
|
56
|
+
if spec.key?('version')
|
57
|
+
spec['version']
|
58
|
+
else
|
59
|
+
Wrapture::VERSION
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|