wrapture 0.2.2 → 0.3.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.
@@ -1,34 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'wrapture/normalize'
4
+
3
5
  module Wrapture
4
- ##
5
6
  # A description of a constant.
6
7
  class ConstantSpec
8
+ # Normalizes a hash specification of a constant. Normalization will check
9
+ # for things like invalid keys, duplicate entries in include lists, and
10
+ # will set missing keys to their default value (for example, an empty list
11
+ # if no includes are given).
7
12
  def self.normalize_spec_hash(spec)
8
- normalized_spec = spec.dup
13
+ normalized = spec.dup
9
14
 
10
- normalized_spec['includes'] ||= []
11
- normalized_spec['includes'].uniq!
15
+ normalized['version'] = Wrapture.spec_version(spec)
16
+ normalized['includes'] = Wrapture.normalize_includes spec['includes']
12
17
 
13
- normalized_spec
18
+ normalized
14
19
  end
15
20
 
21
+ # Creates a constant spec based on the provided hash spec
22
+ #
23
+ # The hash must have the following keys:
24
+ # name:: the name of the constant
25
+ # type:: the type of the constant
26
+ # value:: the value to assign to the constant
27
+ # includes:: a list of includes that need to be added in order for this
28
+ # constant to be valid (for example, includes for the type and value).
16
29
  def initialize(spec)
17
30
  @spec = ConstantSpec.normalize_spec_hash spec
18
31
  end
19
32
 
33
+ # A list of includes needed for the declaration of this constant.
20
34
  def declaration_includes
21
35
  @spec['includes'].dup
22
36
  end
23
37
 
38
+ # A list of includes needed for the definition of this constant.
24
39
  def definition_includes
25
40
  @spec['includes'].dup
26
41
  end
27
42
 
43
+ # The declaration of this constant.
28
44
  def declaration
29
45
  "static const #{ClassSpec.typed_variable(@spec['type'], @spec['name'])}"
30
46
  end
31
47
 
48
+ # The definition of this constant.
32
49
  def definition(class_name)
33
50
  expanded_name = "#{class_name}::#{@spec['name']}"
34
51
  "const #{@spec['type']} #{expanded_name} = #{@spec['value']}"
@@ -0,0 +1,32 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
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
+
19
+ module Wrapture
20
+ # A string denoting an equivalent struct type or value.
21
+ EQUIVALENT_STRUCT_KEYWORD = 'equivalent-struct'
22
+
23
+ # A string denoting a pointer to an equivalent struct type or value.
24
+ EQUIVALENT_POINTER_KEYWORD = 'equivalent-struct-pointer'
25
+
26
+ # A string denoting the return value of a wrapped function call.
27
+ RETURN_VALUE_KEYWORD = 'return-value'
28
+
29
+ # A list of all keywords.
30
+ KEYWORDS = [EQUIVALENT_STRUCT_KEYWORD, EQUIVALENT_POINTER_KEYWORD,
31
+ RETURN_VALUE_KEYWORD].freeze
32
+ end
@@ -0,0 +1,57 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ # Copyright 2019 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
+
19
+ module Wrapture
20
+ # An error from the Wrapture library
21
+ class WraptureError < StandardError
22
+ end
23
+
24
+ # The spec has a key that is not valid.
25
+ class InvalidSpecKey < WraptureError
26
+ # Creates an InvalidSpecKey with the given message. A list of valid values
27
+ # may optionally be passed to +valid_keys+ which will be added to the end
28
+ # of the message.
29
+ def initialize(message, valid_keys: [])
30
+ complete_message = message.dup
31
+
32
+ unless valid_keys.empty?
33
+ complete_message << ' (valid values are \''
34
+ complete_message << valid_keys.join('\', \'')
35
+ complete_message << '\')'
36
+ end
37
+
38
+ super(complete_message)
39
+ end
40
+ end
41
+
42
+ # The spec is missing a key that is required.
43
+ class MissingSpecKey < WraptureError
44
+ # Creates a MissingSpecKey with the given message.
45
+ def initialize(message)
46
+ super(message)
47
+ end
48
+ end
49
+
50
+ # Missing a namespace in the class spec
51
+ class NoNamespace < WraptureError
52
+ end
53
+
54
+ # The spec version is not supported by this version of Wrapture.
55
+ class UnsupportedSpecVersion < WraptureError
56
+ end
57
+ end
@@ -1,74 +1,245 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
1
3
  # frozen_string_literal: true
2
4
 
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
+
19
+ require 'wrapture/constants'
20
+ require 'wrapture/errors'
21
+ require 'wrapture/scope'
22
+ require 'wrapture/wrapped_function_spec'
23
+
3
24
  module Wrapture
4
- ##
5
25
  # A description of a function to be generated, including details about the
6
26
  # underlying implementation.
7
27
  class FunctionSpec
28
+ # Normalizes a hash specification of a function. Normalization will check
29
+ # for things like invalid keys, duplicate entries in include lists, and will
30
+ # set missing keys to their default values (for example, an empty list if no
31
+ # includes are given).
8
32
  def self.normalize_spec_hash(spec)
9
- normalized_spec = spec.dup
33
+ normalized = spec.dup
34
+ param_types = {}
10
35
 
11
- normalized_spec['params'] ||= []
12
- normalized_spec['wrapped-function']['params'] ||= []
13
- normalized_spec['wrapped-function']['includes'] ||= []
36
+ normalized['version'] = Wrapture.spec_version(spec)
37
+ normalized['virtual'] = Wrapture.normalize_boolean(spec, 'virtual')
14
38
 
15
- if normalized_spec['return'].nil?
16
- normalized_spec['return'] = {}
17
- normalized_spec['return']['type'] = 'void'
39
+ normalized['params'] ||= []
40
+ normalized['params'].each do |param_spec|
41
+ param_types[param_spec['name']] = param_spec['type']
42
+ includes = Wrapture.normalize_includes(param_spec['includes'])
43
+ param_spec['includes'] = includes
18
44
  end
19
45
 
20
- normalized_spec['return']['includes'] ||= []
21
-
22
- normalized_spec
23
- end
24
-
25
- def self.param_list(spec)
26
- return 'void' if spec['params'].empty?
27
-
28
- params = []
29
-
30
- spec['params'].each do |param|
31
- params << ClassSpec.typed_variable(param['type'], param['name'])
46
+ if normalized['return'].nil?
47
+ normalized['return'] = {}
48
+ normalized['return']['type'] = 'void'
49
+ normalized['return']['includes'] = []
50
+ else
51
+ normalized['return']['type'] ||= 'void'
52
+ includes = Wrapture.normalize_includes(spec['return']['includes'])
53
+ normalized['return']['includes'] = includes
32
54
  end
33
55
 
34
- params.join ', '
56
+ overload = Wrapture.normalize_boolean(normalized['return'], 'overloaded')
57
+ normalized['return']['overloaded'] = overload
58
+
59
+ normalized
35
60
  end
36
61
 
37
- def initialize(spec, owner)
62
+ # Creates a function spec based on the provided function spec.
63
+ #
64
+ # The hash must have the following keys:
65
+ # name:: the name of the function
66
+ # params:: a list of parameter specifications
67
+ # wrapped-function:: a hash describing the function to be wrapped
68
+ #
69
+ # The wrapped-function must have a 'name' key with the name of the function,
70
+ # and a 'params' key with a list of parameters (each a hash with a 'name'
71
+ # and 'type' key). Optionally, it may also include an 'includes' key with a
72
+ # list of includes that are needed for this function to compile.
73
+ #
74
+ # The following keys are optional:
75
+ # static:: set to true if this is a static function.
76
+ def initialize(spec, owner = Scope.new, constructor: false,
77
+ destructor: false)
38
78
  @owner = owner
39
79
  @spec = FunctionSpec.normalize_spec_hash(spec)
80
+ @wrapped = WrappedFunctionSpec.new(spec['wrapped-function'])
81
+ @constructor = constructor
82
+ @destructor = destructor
40
83
  end
41
84
 
42
- def declaration_includes
43
- @spec['return']['includes'].dup
85
+ # True if the function is a constructor, false otherwise.
86
+ def constructor?
87
+ @constructor
44
88
  end
45
89
 
46
- def definition_includes
90
+ # A list of includes needed for the declaration of the function.
91
+ def declaration_includes
47
92
  includes = @spec['return']['includes'].dup
48
- includes.concat @spec['wrapped-function']['includes']
93
+ includes.concat(param_includes)
94
+ includes.uniq
95
+ end
49
96
 
97
+ # A list of includes needed for the definition of the function.
98
+ def definition_includes
99
+ includes = @wrapped.includes
100
+ includes.concat(@spec['return']['includes'])
101
+ includes.concat(param_includes)
50
102
  includes.uniq
51
103
  end
52
104
 
105
+ # A comma-separated list of parameters and resolved types fit for use in a
106
+ # function signature or declaration.
107
+ def param_list
108
+ return 'void' if @spec['params'].empty?
109
+
110
+ params = []
111
+
112
+ @spec['params'].each do |param|
113
+ type = resolve_type(param['type'])
114
+ params << ClassSpec.typed_variable(type, param['name'])
115
+ end
116
+
117
+ params.join(', ')
118
+ end
119
+
120
+ # Gives an expression for calling a given parameter within this function.
121
+ # Equivalent structs and pointers are resolved, as well as casts between
122
+ # types if they are known within the scope of this function.
123
+ def resolve_wrapped_param(param_spec)
124
+ used_param = @spec['params'].find { |p| p['name'] == param_spec['value'] }
125
+
126
+ if param_spec['value'] == EQUIVALENT_STRUCT_KEYWORD
127
+ @owner.this_struct
128
+ elsif param_spec['value'] == EQUIVALENT_POINTER_KEYWORD
129
+ @owner.this_struct_pointer
130
+ elsif used_param &&
131
+ @owner.type?(used_param['type']) &&
132
+ !param_spec['type'].nil?
133
+ param_class = @owner.type(used_param['type'])
134
+ param_class.cast_to(used_param['name'], param_spec['type'])
135
+ else
136
+ param_spec['value']
137
+ end
138
+ end
139
+
140
+ # The signature of the function.
53
141
  def signature
54
- "#{@spec['name']}( #{FunctionSpec.param_list @spec} )"
142
+ "#{@spec['name']}( #{param_list} )"
55
143
  end
56
144
 
145
+ # The declaration of the function.
57
146
  def declaration
58
- modifier_prefix = @spec['static'] ? 'static ' : ''
147
+ return signature if @constructor || @destructor
148
+
149
+ modifier_prefix = if @spec['static']
150
+ 'static '
151
+ elsif virtual?
152
+ 'virtual '
153
+ else
154
+ ''
155
+ end
59
156
  "#{modifier_prefix}#{@spec['return']['type']} #{signature}"
60
157
  end
61
158
 
159
+ # Gives the definition of the function to a block, line by line.
62
160
  def definition(class_name)
63
- return_type = @spec['return']['type']
64
- yield "#{return_type} #{class_name}::#{signature} {"
65
-
66
- wrapped_call = String.new
67
- wrapped_call << "return #{return_type} ( " unless return_type == 'void'
68
- wrapped_call << @owner.function_call(@spec['wrapped-function'])
69
- wrapped_call << ' )' unless return_type == 'void'
70
- yield " #{wrapped_call};"
161
+ yield "#{return_prefix}#{class_name}::#{signature} {"
162
+
163
+ if @wrapped.error_check?
164
+ yield " #{@wrapped.return_val_type} return_val;"
165
+ yield
166
+ end
167
+
168
+ call = @wrapped.call_from(self)
169
+ call_line = if @constructor
170
+ "this->equivalent = #{call}"
171
+ elsif @wrapped.error_check?
172
+ "return_val = #{call}"
173
+ elsif returns_value?
174
+ "return #{return_cast(call)}"
175
+ else
176
+ call
177
+ end
178
+
179
+ yield " #{call_line};"
180
+
181
+ if @wrapped.error_check?
182
+ yield
183
+ @wrapped.error_check { |line| yield " #{line}" }
184
+ end
185
+
71
186
  yield '}'
72
187
  end
188
+
189
+ # True if the function is virtual.
190
+ def virtual?
191
+ @spec['virtual']
192
+ end
193
+
194
+ private
195
+
196
+ # A list of includes needed for the parameters of the function.
197
+ def param_includes
198
+ includes = []
199
+
200
+ @spec['params'].each do |param_spec|
201
+ includes.concat(param_spec['includes'])
202
+ end
203
+
204
+ includes
205
+ end
206
+
207
+ # A resolved type name.
208
+ def resolve_type(type)
209
+ if type == EQUIVALENT_STRUCT_KEYWORD
210
+ "struct #{@owner.struct_name}"
211
+ elsif type == EQUIVALENT_POINTER_KEYWORD
212
+ "struct #{@owner.struct_name} *"
213
+ else
214
+ type
215
+ end
216
+ end
217
+
218
+ # The function to use to create the return value of the function.
219
+ def return_cast(value)
220
+ if @spec['return']['type'] == @wrapped.return_val_type
221
+ value
222
+ elsif @spec['return']['overloaded']
223
+ "new#{@spec['return']['type'].chomp('*').strip} ( #{value} )"
224
+ else
225
+ "#{@spec['return']['type']} ( #{value} )"
226
+ end
227
+ end
228
+
229
+ # The return type prefix to use for the function definition.
230
+ def return_prefix
231
+ if @constructor || @destructor
232
+ ''
233
+ elsif @spec['return']['type'].end_with?('*')
234
+ @spec['return']['type']
235
+ else
236
+ "#{@spec['return']['type']} "
237
+ end
238
+ end
239
+
240
+ # True if the function returns a value.
241
+ def returns_value?
242
+ !@constructor && !@destructor && @spec['return']['type'] != 'void'
243
+ end
73
244
  end
74
245
  end
@@ -0,0 +1,62 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ # Copyright 2019 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
+
19
+ require 'wrapture/version'
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