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
@@ -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
|