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.
@@ -0,0 +1,132 @@
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
+ require 'wrapture/type_spec'
22
+
23
+ module Wrapture
24
+ # A description of a parameter used in a generated function.
25
+ class ParamSpec
26
+ # Returns a list of new ParamSpecs based on the provided array of parameter
27
+ # specification hashes.
28
+ def self.new_list(spec_list)
29
+ spec_list.map { |spec| new(spec) }
30
+ end
31
+
32
+ # Returns a normalized copy of a list of parameter hash specifications in
33
+ # place.
34
+ #
35
+ # Multiple variadic parameters (named '...') will be removed and only the
36
+ # first used. If the variadic parameter is not last, it will be moved to
37
+ # the end of the list.
38
+ def self.normalize_param_list(spec_list)
39
+ if spec_list.nil?
40
+ []
41
+ elsif spec_list.count { |spec| spec['name'] == '...' }.zero?
42
+ spec_list.map { |spec| normalize_spec_hash(spec) }
43
+ else
44
+ error_msg = "'...' may not be the only parameter"
45
+ raise(InvalidSpecKey, error_msg) if spec_list.count == 1
46
+
47
+ i = spec_list.find_index { |spec| spec['name'] == '...' }
48
+ var = spec_list[i]
49
+
50
+ spec_list
51
+ .reject { |spec| spec['name'] == '...' }
52
+ .map { |spec| normalize_spec_hash(spec) }
53
+ .push(var)
54
+ end
55
+ end
56
+
57
+ # Returns a normalized copy of the hash specification of a parameter in
58
+ # +spec+. See normalize_spec_hash! for details.
59
+ def self.normalize_spec_hash(spec)
60
+ normalize_spec_hash!(Marshal.load(Marshal.dump(spec)))
61
+ end
62
+
63
+ # Normalizes the hash specification of a parameter in +spec+ in place.
64
+ # Normalization will remove duplicate entries from include lists and
65
+ # validate that required key values are set.
66
+ def self.normalize_spec_hash!(spec)
67
+ Comment.validate_doc(spec['doc']) if spec.key?('doc')
68
+ spec['includes'] = Wrapture.normalize_includes(spec['includes'])
69
+
70
+ spec['type'] = '...' if spec['name'] == '...'
71
+
72
+ unless spec.key?('type')
73
+ missing_type_msg = 'parameters must have a type key defined'
74
+ raise(MissingSpecKey, missing_type_msg)
75
+ end
76
+
77
+ spec
78
+ end
79
+
80
+ # A string with a comma-separated list of parameters (using resolved type)
81
+ # and names, fit for use in a function signature or declaration. param_list
82
+ # must be a list of ParamSpec instances, and owner must be the FunctionSpec
83
+ # that the parameters belong to.
84
+ def self.signature(param_list, owner)
85
+ if param_list.empty?
86
+ 'void'
87
+ else
88
+ param_list.map { |param| param.signature(owner) }.join(', ')
89
+ end
90
+ end
91
+
92
+ # The type of the parameter.
93
+ attr_reader :type
94
+
95
+ # Creates a parameter specification based on the provided hash spec.
96
+ def initialize(spec)
97
+ @spec = ParamSpec.normalize_spec_hash(spec)
98
+ @type = TypeSpec.new(@spec['type'])
99
+ end
100
+
101
+ # A Comment holding the parameter documentation.
102
+ def doc
103
+ if @spec.key?('doc')
104
+ Comment.new("@param #{@spec['name']} #{@spec['doc']}")
105
+ else
106
+ Comment.new
107
+ end
108
+ end
109
+
110
+ # A list of includes needed for this parameter.
111
+ def includes
112
+ @spec['includes'].dup.concat(@type.includes)
113
+ end
114
+
115
+ # The name of the parameter.
116
+ def name
117
+ @spec['name']
118
+ end
119
+
120
+ # The parameter type and name, suitable for use in a function signature or
121
+ # declaration. +owner+ must be the FunctionSpec that the parameter belongs
122
+ # to.
123
+ def signature(owner)
124
+ @type.resolve(owner).variable(name)
125
+ end
126
+
127
+ # True if this parameter is variadic (the name is equal to '...').
128
+ def variadic?
129
+ @type.variadic?
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,118 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
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
+
21
+ module Wrapture
22
+ # A condition (or set of conditions) that a struct or its members must meet
23
+ # in order to conform to a given specification. This allows a single struct
24
+ # type to be equivalent to some class specifications, but not others.
25
+ class RuleSpec
26
+ # A map of condition strings to their operators.
27
+ CONDITIONS = { 'equals' => '==',
28
+ 'greater-than' => '>',
29
+ 'greater-than-equal' => '>=',
30
+ 'less-than' => '<',
31
+ 'less-than-equal' => '<=',
32
+ 'not-equals' => '!=' }.freeze
33
+
34
+ # Normalizes a hash specification of a rule. Normalization checks for
35
+ # invalid keys and unrecognized conditions.
36
+ def self.normalize_spec_hash(spec)
37
+ normalized = spec.dup
38
+
39
+ required_keys = if spec.key?('member-name')
40
+ normalized['type'] = 'struct-member'
41
+ %w[member-name condition value].freeze
42
+ else
43
+ normalized['type'] = 'expression'
44
+ %w[left-expression condition right-expression].freeze
45
+ end
46
+
47
+ missing_keys = required_keys - spec.keys
48
+ unless missing_keys.empty?
49
+ missing_msg = "required keys are missing: #{missing_keys.join(', ')}"
50
+ raise(MissingSpecKey, missing_msg)
51
+ end
52
+
53
+ extra_keys = spec.keys - required_keys
54
+ unless extra_keys.empty?
55
+ extra_msg = "these keys are unrecognized: #{extra_keys.join(', ')}"
56
+ raise(InvalidSpecKey, extra_msg)
57
+ end
58
+
59
+ unless RuleSpec::CONDITIONS.keys.include?(spec['condition'])
60
+ condition_msg = "#{spec['condition']} is an invalid condition"
61
+ raise(InvalidSpecKey, condition_msg)
62
+ end
63
+
64
+ normalized
65
+ end
66
+
67
+ # Creates a rule spec based on the provided spec. Rules may be one of a
68
+ # number of different varieties.
69
+ #
70
+ # Available conditions are available in the RuleSpec::CONDITIONS map, with
71
+ # the mapped values being the operate each one translates to.
72
+ #
73
+ # For a rule that checks a struct member against a given value (a
74
+ # +struct-member+ rule):
75
+ # member-name:: the name of the struct member the rule applies to
76
+ # condition:: the condition this rule uses
77
+ # value:: the value to use in the condition check
78
+ #
79
+ # For a rule that compares two expressions against one another (an
80
+ # +expression+ rule):
81
+ # left-expression:: the left expression in the comparison
82
+ # condition:: the condition this rule uses
83
+ # right-expression:: the right expression in the comparison
84
+ def initialize(spec)
85
+ @spec = RuleSpec.normalize_spec_hash(spec)
86
+ end
87
+
88
+ # A string containing a check for a struct of the given name for this rule.
89
+ #
90
+ # +variable+ can be provided to provide the variable holding a struct
91
+ # pointer for +struct-member+ rules.
92
+ #
93
+ # +return_val+ is used as the replacement for a return value signified by
94
+ # the use of RETURN_VALUE_KEYWORD in the spec. If not specified it defaults
95
+ # to +'return_val'+. This parameter was added in release 0.4.2.
96
+ def check(variable: nil, return_val: 'return_val')
97
+ condition = RuleSpec::CONDITIONS[@spec['condition']]
98
+
99
+ if @spec['type'] == 'struct-member'
100
+ "#{variable}->#{@spec['member-name']} #{condition} #{@spec['value']}"
101
+ else
102
+ left = @spec['left-expression']
103
+ right = @spec['right-expression']
104
+ "#{left} #{condition} #{right}".sub(RETURN_VALUE_KEYWORD, return_val)
105
+ end
106
+ end
107
+
108
+ # True if this rule requires a return value. This is equivalent to checking
109
+ # for the presence of RETURN_VALUE_KEYWORD in any of the expressions.
110
+ #
111
+ # This method was added in release 0.4.2.
112
+ def use_return?
113
+ @spec['type'] == 'expression' &&
114
+ [@spec['left-expression'],
115
+ @spec['right-expression']].include?(RETURN_VALUE_KEYWORD)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,112 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
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
+
21
+ module Wrapture
22
+ # Describes a scope of one or more class specifications.
23
+ class Scope
24
+ # A list of classes currently in the scope.
25
+ attr_reader :classes
26
+
27
+ # A list of enumerations currently in the scope.
28
+ attr_reader :enums
29
+
30
+ # A list of the templates defined in the scope.
31
+ attr_reader :templates
32
+
33
+ # Creates an empty scope with no classes in it.
34
+ def initialize(spec = nil)
35
+ @classes = []
36
+ @enums = []
37
+ @templates = []
38
+
39
+ return if spec.nil?
40
+
41
+ @version = Wrapture.spec_version(spec)
42
+
43
+ @templates = spec.fetch('templates', []).collect do |template_hash|
44
+ TemplateSpec.new(template_hash)
45
+ end
46
+
47
+ @classes = spec.fetch('classes', []).collect do |class_hash|
48
+ ClassSpec.new(class_hash, scope: self)
49
+ end
50
+
51
+ @enums = spec.fetch('enums', []).collect do |enum_hash|
52
+ EnumSpec.new(enum_hash)
53
+ end
54
+ end
55
+
56
+ # Adds a class or template specification to the scope.
57
+ #
58
+ # This does not set the scope as the owner of the class for a ClassSpec.
59
+ # This must be done during the construction of the class spec.
60
+ def <<(spec)
61
+ @templates << spec if spec.is_a?(TemplateSpec)
62
+ @classes << spec if spec.is_a?(ClassSpec)
63
+ @enums << spec if spec.is_a?(EnumSpec)
64
+ end
65
+
66
+ # Adds a class to the scope created from the given specification hash.
67
+ def add_class_spec_hash(spec)
68
+ ClassSpec.new(spec, scope: self)
69
+ end
70
+
71
+ # Adds an enumeration to the scope created from the given specification
72
+ # hash.
73
+ def add_enum_spec_hash(spec)
74
+ @enums << EnumSpec.new(spec)
75
+ end
76
+
77
+ # Generates the wrapper class files for all classes in the scope.
78
+ def generate_wrappers
79
+ files = []
80
+
81
+ @classes.each do |class_spec|
82
+ files.concat(class_spec.generate_wrappers)
83
+ end
84
+
85
+ @enums.each do |enum_spec|
86
+ files.concat(enum_spec.generate_wrapper)
87
+ end
88
+
89
+ files
90
+ end
91
+
92
+ # A list of ClassSpecs in this scope that are overloads of the given class.
93
+ def overloads(parent)
94
+ @classes.select { |class_spec| class_spec.overloads?(parent) }
95
+ end
96
+
97
+ # True if there is an overload of the given class in this scope.
98
+ def overloads?(parent)
99
+ @classes.any? { |class_spec| class_spec.overloads?(parent) }
100
+ end
101
+
102
+ # Returns the ClassSpec for the given +type+ in the scope, if one exists.
103
+ def type(type)
104
+ @classes.find { |class_spec| class_spec.name == type.base }
105
+ end
106
+
107
+ # Returns true if there is a class matching the given +type+ in this scope.
108
+ def type?(type)
109
+ @classes.any? { |class_spec| class_spec.name == type.base }
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,129 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
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
+
21
+ module Wrapture
22
+ # A description of a struct.
23
+ class StructSpec
24
+ # Normalizes a hash specification of a struct. Normalization will check for
25
+ # things like invalid keys, duplicate entries in include lists, and will set
26
+ # missing keys to their default value (for example, an empty list if no
27
+ # includes are given).
28
+ def self.normalize_spec_hash(spec)
29
+ normalized = spec.dup
30
+ normalized.default = []
31
+
32
+ normalized['includes'] = Wrapture.normalize_includes spec['includes']
33
+
34
+ normalized['members'] ||= []
35
+
36
+ normalized
37
+ end
38
+
39
+ # A list of rules defined for this struct.
40
+ attr_reader :rules
41
+
42
+ # Creates a struct spec based on the provided spec hash.
43
+ #
44
+ # The hash must have the following keys:
45
+ # name:: the name of the struct
46
+ #
47
+ # The following keys are optional:
48
+ # includes:: a list of includes required for the struct
49
+ # members:: a list of the members of the struct, each with a type and name
50
+ # field
51
+ # rules:: a list of conditions this struct and its members must meet (refer
52
+ # to the RuleSpec class for more details)
53
+ def initialize(spec)
54
+ @spec = StructSpec.normalize_spec_hash(spec)
55
+
56
+ @rules = @spec['rules'].map { |rule_spec| RuleSpec.new(rule_spec) }
57
+ end
58
+
59
+ # A declaration of the struct with the given variable name.
60
+ def declaration(name)
61
+ "struct #{@spec['name']} #{name}"
62
+ end
63
+
64
+ # A list of includes required for this struct.
65
+ def includes
66
+ @spec['includes'].dup
67
+ end
68
+
69
+ # A string containing the typed members of the struct, separated by commas.
70
+ def member_list
71
+ members = []
72
+
73
+ @spec['members'].each do |member|
74
+ members << TypeSpec.new(member['type']).variable(member['name'])
75
+ end
76
+
77
+ members.join ', '
78
+ end
79
+
80
+ # A string containing the typed members of the struct, with their default
81
+ # values if provided, separated by commas.
82
+ def member_list_with_defaults
83
+ @spec['members'].map do |member|
84
+ member_str = TypeSpec.new(member['type']).variable(member['name'])
85
+
86
+ if member.key?('default-value')
87
+ default_value = member['default-value']
88
+
89
+ member_str += ' = '
90
+ member_str += if member['type'] == 'const char *'
91
+ "\"#{default_value}\""
92
+ elsif member['type'].end_with?('char')
93
+ "'#{default_value}'"
94
+ else
95
+ default_value.to_s
96
+ end
97
+ end
98
+
99
+ member_str
100
+ end.join(', ')
101
+ end
102
+
103
+ # The members of the struct
104
+ def members
105
+ @spec['members']
106
+ end
107
+
108
+ # True if there are members included in the struct specification.
109
+ def members?
110
+ !@spec['members'].empty?
111
+ end
112
+
113
+ # The name of this struct
114
+ def name
115
+ @spec['name']
116
+ end
117
+
118
+ # A declaration of a pointer to the struct with the given variable name.
119
+ def pointer_declaration(name)
120
+ "struct #{@spec['name']} *#{name}"
121
+ end
122
+
123
+ # A string containing an expression that returns true if the struct with
124
+ # the given name meets all rules defined for this struct.
125
+ def rules_check(name)
126
+ @rules.map { |rule| rule.check(variable: name) }.join(' && ')
127
+ end
128
+ end
129
+ end