ruby-bindgen 1.0.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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +25 -0
- data/README.md +68 -0
- data/Rakefile +8 -0
- data/bin/ruby-bindgen +133 -0
- data/docs/architecture.md +238 -0
- data/docs/c/c_bindings.md +65 -0
- data/docs/c/constants.md +21 -0
- data/docs/c/customizing.md +19 -0
- data/docs/c/filtering.md +24 -0
- data/docs/c/getting_started.md +56 -0
- data/docs/c/library_loading.md +53 -0
- data/docs/c/output.md +96 -0
- data/docs/c/types.md +61 -0
- data/docs/c/version_guards.md +105 -0
- data/docs/cmake/cmake_bindings.md +19 -0
- data/docs/cmake/filtering.md +26 -0
- data/docs/cmake/getting_started.md +52 -0
- data/docs/cmake/output.md +110 -0
- data/docs/configuration.md +351 -0
- data/docs/contributing.md +68 -0
- data/docs/cpp/buffers.md +24 -0
- data/docs/cpp/classes.md +139 -0
- data/docs/cpp/cpp_bindings.md +29 -0
- data/docs/cpp/customizing.md +124 -0
- data/docs/cpp/enums.md +42 -0
- data/docs/cpp/filtering.md +35 -0
- data/docs/cpp/getting_started.md +80 -0
- data/docs/cpp/iterators.md +94 -0
- data/docs/cpp/operators.md +170 -0
- data/docs/cpp/output.md +125 -0
- data/docs/cpp/templates.md +114 -0
- data/docs/examples.md +133 -0
- data/docs/index.md +133 -0
- data/docs/prior_art.md +37 -0
- data/docs/troubleshooting.md +243 -0
- data/docs/type_spelling.md +200 -0
- data/docs/updating_bindings.md +55 -0
- data/docs/version_guards.md +69 -0
- data/lib/ruby-bindgen/config.rb +63 -0
- data/lib/ruby-bindgen/generators/cmake/cmake.rb +171 -0
- data/lib/ruby-bindgen/generators/cmake/directory.erb +29 -0
- data/lib/ruby-bindgen/generators/cmake/guard.rb +55 -0
- data/lib/ruby-bindgen/generators/cmake/presets.erb +232 -0
- data/lib/ruby-bindgen/generators/cmake/project.erb +89 -0
- data/lib/ruby-bindgen/generators/ffi/callback.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/constant.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/enum_constant_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/enum_decl.erb +4 -0
- data/lib/ruby-bindgen/generators/ffi/enum_decl_anonymous.erb +4 -0
- data/lib/ruby-bindgen/generators/ffi/ffi.rb +687 -0
- data/lib/ruby-bindgen/generators/ffi/field_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/function.erb +5 -0
- data/lib/ruby-bindgen/generators/ffi/library.erb +39 -0
- data/lib/ruby-bindgen/generators/ffi/project.erb +18 -0
- data/lib/ruby-bindgen/generators/ffi/struct.erb +6 -0
- data/lib/ruby-bindgen/generators/ffi/translation_unit.erb +9 -0
- data/lib/ruby-bindgen/generators/ffi/typedef_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/union.erb +6 -0
- data/lib/ruby-bindgen/generators/ffi/variable.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/version.erb +9 -0
- data/lib/ruby-bindgen/generators/ffi/version_method.erb +5 -0
- data/lib/ruby-bindgen/generators/generator.rb +52 -0
- data/lib/ruby-bindgen/generators/rice/auto_generated_base_class.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/class.erb +31 -0
- data/lib/ruby-bindgen/generators/rice/class_template.erb +9 -0
- data/lib/ruby-bindgen/generators/rice/class_template_specialization.erb +10 -0
- data/lib/ruby-bindgen/generators/rice/constant.erb +9 -0
- data/lib/ruby-bindgen/generators/rice/constructor.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/conversion_function.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/cxx_iterator_method.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/cxx_method.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/enum_constant_decl.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/enum_decl.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/field_decl.erb +8 -0
- data/lib/ruby-bindgen/generators/rice/function.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/function_pointer.rb +68 -0
- data/lib/ruby-bindgen/generators/rice/incomplete_class.erb +3 -0
- data/lib/ruby-bindgen/generators/rice/iterator_alias.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/iterator_collector.rb +159 -0
- data/lib/ruby-bindgen/generators/rice/namespace.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_binary.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_inspect.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_unary.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/operator[].erb +4 -0
- data/lib/ruby-bindgen/generators/rice/project.cpp.erb +18 -0
- data/lib/ruby-bindgen/generators/rice/project.hpp.erb +13 -0
- data/lib/ruby-bindgen/generators/rice/reference_qualifier.rb +495 -0
- data/lib/ruby-bindgen/generators/rice/rice.rb +1724 -0
- data/lib/ruby-bindgen/generators/rice/rice_include.hpp.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/signature_builder.rb +230 -0
- data/lib/ruby-bindgen/generators/rice/template_resolver.rb +585 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.cpp.erb +40 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.hpp.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.ipp.erb +3 -0
- data/lib/ruby-bindgen/generators/rice/type_index.rb +117 -0
- data/lib/ruby-bindgen/generators/rice/type_speller.rb +509 -0
- data/lib/ruby-bindgen/generators/rice/union.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/variable.erb +5 -0
- data/lib/ruby-bindgen/inputter.rb +54 -0
- data/lib/ruby-bindgen/name_mapper.rb +65 -0
- data/lib/ruby-bindgen/namer.rb +138 -0
- data/lib/ruby-bindgen/outputter.rb +40 -0
- data/lib/ruby-bindgen/parser.rb +82 -0
- data/lib/ruby-bindgen/refinements/cursor.rb +57 -0
- data/lib/ruby-bindgen/refinements/string.rb +41 -0
- data/lib/ruby-bindgen/symbol_candidates.rb +282 -0
- data/lib/ruby-bindgen/symbol_entry.rb +21 -0
- data/lib/ruby-bindgen/symbols.rb +107 -0
- data/lib/ruby-bindgen/type_pointer_formatter.rb +35 -0
- data/lib/ruby-bindgen/version.rb +3 -0
- data/lib/ruby-bindgen.rb +19 -0
- data/ruby-bindgen.gemspec +52 -0
- metadata +260 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module RubyBindgen
|
|
2
|
+
module Generators
|
|
3
|
+
class Rice
|
|
4
|
+
# Builds shared lookups for typedef resolution and simple-name qualification.
|
|
5
|
+
# Rice uses this index while walking the AST instead of threading raw hashes
|
|
6
|
+
# through unrelated qualification and inheritance code paths.
|
|
7
|
+
class TypeIndex
|
|
8
|
+
def initialize
|
|
9
|
+
clear
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Reset both lookup tables before a fresh AST walk.
|
|
13
|
+
#
|
|
14
|
+
# `@typedefs` maps canonical type spellings to the typedef/alias cursor
|
|
15
|
+
# that should win for that canonical type, for example:
|
|
16
|
+
# "int" => cursor for `using MyInt = int`
|
|
17
|
+
#
|
|
18
|
+
# `@qualified_names` maps a simple name to the preferred qualified name:
|
|
19
|
+
# "Box" => "Example::Box"
|
|
20
|
+
def clear
|
|
21
|
+
@typedefs = {}
|
|
22
|
+
@qualified_names = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Walk the translation unit once and collect the shared type lookups used
|
|
26
|
+
# by the Rice generator.
|
|
27
|
+
#
|
|
28
|
+
# Only top-level typedefs/aliases are indexed. Member aliases are skipped
|
|
29
|
+
# because they are only valid through the owning class scope and cannot be
|
|
30
|
+
# reused as generic replacements elsewhere in generated code.
|
|
31
|
+
def build!(cursor)
|
|
32
|
+
clear
|
|
33
|
+
|
|
34
|
+
cursor.find_by_kind(true, :cursor_typedef_decl, :cursor_type_alias_decl,
|
|
35
|
+
:cursor_class_template, :cursor_class_decl, :cursor_struct) do |child|
|
|
36
|
+
record_type(child)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Find the preferred typedef/alias cursor for a canonical type spelling.
|
|
43
|
+
#
|
|
44
|
+
# Example:
|
|
45
|
+
# typedef_for("int")
|
|
46
|
+
# returns the cursor for `using MyInt = int`
|
|
47
|
+
def typedef_for(canonical_spelling)
|
|
48
|
+
@typedefs[canonical_spelling]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Find the preferred qualified name for a simple type name.
|
|
52
|
+
#
|
|
53
|
+
# Example:
|
|
54
|
+
# qualified_name_for("Box")
|
|
55
|
+
# returns "Example::Box"
|
|
56
|
+
def qualified_name_for(simple_name)
|
|
57
|
+
@qualified_names[simple_name]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Record one discovered cursor into the appropriate lookup table.
|
|
61
|
+
#
|
|
62
|
+
# Typedefs and aliases populate both:
|
|
63
|
+
# - canonical type -> preferred typedef cursor
|
|
64
|
+
# - simple name -> preferred qualified spelling
|
|
65
|
+
#
|
|
66
|
+
# Class/template declarations only populate the simple-name lookup because
|
|
67
|
+
# they do not stand in for another canonical type.
|
|
68
|
+
def record_type(child)
|
|
69
|
+
case child.kind
|
|
70
|
+
when :cursor_typedef_decl, :cursor_type_alias_decl
|
|
71
|
+
parent_kind = child.semantic_parent.kind
|
|
72
|
+
return if parent_kind == :cursor_class_decl || parent_kind == :cursor_struct ||
|
|
73
|
+
parent_kind == :cursor_class_template || parent_kind == :cursor_class_template_partial_specialization
|
|
74
|
+
|
|
75
|
+
canonical = child.underlying_type.canonical.spelling
|
|
76
|
+
existing = @typedefs[canonical]
|
|
77
|
+
if existing.nil? || prefer_replacement?(existing.qualified_name, child.qualified_name)
|
|
78
|
+
@typedefs[canonical] = child
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
record_qualified_name(child.spelling, child.qualified_name)
|
|
82
|
+
when :cursor_class_template, :cursor_class_decl, :cursor_struct
|
|
83
|
+
return if child.spelling.empty?
|
|
84
|
+
|
|
85
|
+
record_qualified_name(child.spelling, child.qualified_name, prefer_existing: true)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Store the preferred qualified spelling for a simple name.
|
|
90
|
+
#
|
|
91
|
+
# `prefer_existing: true` is used for class declarations so an earlier
|
|
92
|
+
# alias like `Example::Box` is not overwritten later by the class/template
|
|
93
|
+
# declaration for the same simple name.
|
|
94
|
+
def record_qualified_name(simple_name, qualified_name, prefer_existing: false)
|
|
95
|
+
return if simple_name.nil? || simple_name.empty?
|
|
96
|
+
return if simple_name == qualified_name
|
|
97
|
+
|
|
98
|
+
existing = @qualified_names[simple_name]
|
|
99
|
+
return if prefer_existing && existing
|
|
100
|
+
|
|
101
|
+
if existing.nil? || prefer_replacement?(existing, qualified_name)
|
|
102
|
+
@qualified_names[simple_name] = qualified_name
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Prefer user/library names over `std::...` when both map to the same
|
|
109
|
+
# simple name. This keeps project-local aliases such as `cv::String`
|
|
110
|
+
# instead of replacing them with `std::string`.
|
|
111
|
+
def prefer_replacement?(existing_qualified_name, new_qualified_name)
|
|
112
|
+
existing_qualified_name.start_with?("std::") && !new_qualified_name.start_with?("std::")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
require_relative '../../type_pointer_formatter'
|
|
2
|
+
|
|
3
|
+
module RubyBindgen
|
|
4
|
+
module Generators
|
|
5
|
+
class Rice
|
|
6
|
+
# Builds fully qualified C++ type spellings for generated Rice code and
|
|
7
|
+
# applies the extra template/class qualification rules that libclang does
|
|
8
|
+
# not handle on its own for emitted binding code.
|
|
9
|
+
class TypeSpeller
|
|
10
|
+
attr_writer :printing_policy
|
|
11
|
+
|
|
12
|
+
def initialize(type_index:)
|
|
13
|
+
@type_index = type_index
|
|
14
|
+
clear
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def clear
|
|
18
|
+
@class_template_typedefs = {}
|
|
19
|
+
@class_static_members = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get the fully qualified class/struct type used in generated bindings.
|
|
23
|
+
#
|
|
24
|
+
# Examples:
|
|
25
|
+
# outer::Locale::Facet<Locale>
|
|
26
|
+
# becomes
|
|
27
|
+
# outer::Locale::Facet<outer::Locale>
|
|
28
|
+
#
|
|
29
|
+
# This goes through type_spelling(cursor.type) instead of cursor-specific
|
|
30
|
+
# string surgery. A class cursor's first child can be an unrelated type_ref,
|
|
31
|
+
# which makes first-match substitution double-qualify nested specializations
|
|
32
|
+
# like outer::outer::Locale::Facet<outer::Locale>.
|
|
33
|
+
def qualified_class_name(cursor)
|
|
34
|
+
type_spelling(cursor.type)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get qualified display name for a cursor.
|
|
38
|
+
# Qualifies any template arguments that need namespace prefixes.
|
|
39
|
+
# Used for generating fully qualified names in enum constants, etc.
|
|
40
|
+
def qualified_display_name(cursor)
|
|
41
|
+
if cursor&.kind == :cursor_class_template
|
|
42
|
+
template_arguments = template_parameter_arguments(cursor)
|
|
43
|
+
return "#{cursor.qualified_name}<#{template_arguments.join(', ')}>" unless template_arguments.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
display_name = qualify_template_parameter_packs(cursor.qualified_display_name, cursor)
|
|
47
|
+
|
|
48
|
+
# For members of template specializations, use the parent's type for qualification
|
|
49
|
+
# e.g., TypeTraits<lowercase_type>::type needs lowercase_type qualified,
|
|
50
|
+
# but cursor.type is just 'const int' which has no template args
|
|
51
|
+
type = cursor.type
|
|
52
|
+
parent = cursor.semantic_parent
|
|
53
|
+
if parent && parent.type.num_template_arguments > 0
|
|
54
|
+
type = parent.type
|
|
55
|
+
end
|
|
56
|
+
qualify_template_args(display_name, type,
|
|
57
|
+
ignored_names: template_parameter_names(cursor))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def type_spellings(cursor)
|
|
61
|
+
cursor.type.arg_types.map do |arg_type|
|
|
62
|
+
type_spelling(arg_type)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns a fully-qualified C++ type spelling suitable for use in generated Rice bindings.
|
|
67
|
+
# Most type kinds are handled by Type#fully_qualified_name. This method only
|
|
68
|
+
# intercepts declared types that need generator-level context:
|
|
69
|
+
# - Class template types: fqn resolves template params, but template builders need them generic
|
|
70
|
+
# - Typedefs inside class templates: need 'typename' keyword for dependent types
|
|
71
|
+
# - Template instantiations: need qualify_template_args post-processing with TypeIndex
|
|
72
|
+
def type_spelling(type)
|
|
73
|
+
case type.kind
|
|
74
|
+
when :type_pointer
|
|
75
|
+
type_spelling_pointer(type)
|
|
76
|
+
when :type_lvalue_ref
|
|
77
|
+
"#{type_spelling(type.non_reference_type)} &"
|
|
78
|
+
when :type_rvalue_ref
|
|
79
|
+
"#{type_spelling(type.non_reference_type)} &&"
|
|
80
|
+
when :type_constant_array
|
|
81
|
+
"#{type_spelling(type.element_type)}[#{type.size}]"
|
|
82
|
+
when :type_incomplete_array
|
|
83
|
+
"#{type_spelling(type.element_type)}[]"
|
|
84
|
+
when :type_elaborated
|
|
85
|
+
type_spelling_declared(type)
|
|
86
|
+
when :type_typedef
|
|
87
|
+
type_spelling_declared(type)
|
|
88
|
+
when :type_unexposed
|
|
89
|
+
type_spelling_unexposed(type)
|
|
90
|
+
else
|
|
91
|
+
type.fully_qualified_name(@printing_policy)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Qualify nested typedefs from a class template in a type spelling
|
|
96
|
+
# e.g., "std::reverse_iterator<iterator>" -> "std::reverse_iterator<cv::Mat_<_Tp>::iterator>"
|
|
97
|
+
def qualify_class_template_typedefs(spelling, class_template)
|
|
98
|
+
return spelling unless class_template&.kind == :cursor_class_template
|
|
99
|
+
|
|
100
|
+
cache_key = class_template.usr
|
|
101
|
+
typedef_info = @class_template_typedefs[cache_key] ||= begin
|
|
102
|
+
names = []
|
|
103
|
+
class_template.each(false) do |child|
|
|
104
|
+
child_kind = child.kind
|
|
105
|
+
if child_kind == :cursor_typedef_decl || child_kind == :cursor_type_alias_decl
|
|
106
|
+
names << child.spelling
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
{ names: names, qualified_parent: class_template.qualified_display_name }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
return spelling if typedef_info[:names].empty?
|
|
113
|
+
|
|
114
|
+
result = spelling.dup
|
|
115
|
+
qualified_parent = typedef_info[:qualified_parent]
|
|
116
|
+
qualified_name = class_template.qualified_name
|
|
117
|
+
display_name = class_template.display_name
|
|
118
|
+
simple_name = class_template.spelling
|
|
119
|
+
typedef_info[:names].each do |name|
|
|
120
|
+
fully_qualified = /(?<![:\w])(?:typename\s+)?#{Regexp.escape(qualified_parent)}::#{Regexp.escape(name)}(?![:\w])/
|
|
121
|
+
result = result.gsub(fully_qualified, "typename #{qualified_parent}::#{name}")
|
|
122
|
+
|
|
123
|
+
if qualified_name && !qualified_name.empty? && qualified_name != qualified_parent
|
|
124
|
+
qualified_without_args = /(?<![:\w])#{Regexp.escape(qualified_name)}::#{Regexp.escape(name)}(?![:\w])/
|
|
125
|
+
result = result.gsub(qualified_without_args, "typename #{qualified_parent}::#{name}")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if display_name && !display_name.empty? && display_name != qualified_parent
|
|
129
|
+
partially_qualified = /(?<![:\w])#{Regexp.escape(display_name)}::#{Regexp.escape(name)}(?![:\w])/
|
|
130
|
+
result = result.gsub(partially_qualified, "typename #{qualified_parent}::#{name}")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if simple_name && !simple_name.empty?
|
|
134
|
+
uninstantiated_class = /(?<![:\w])#{Regexp.escape(simple_name)}::#{Regexp.escape(name)}(?![:\w])/
|
|
135
|
+
result = result.gsub(uninstantiated_class, "typename #{qualified_parent}::#{name}")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
unqualified = /(?<![:\w])#{Regexp.escape(name)}(?![:\w])/
|
|
139
|
+
result = result.gsub(unqualified, "typename #{qualified_parent}::#{name}")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
result
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Qualify bare class members used as non-type template args.
|
|
146
|
+
# Within a class like GPCPatchDescriptor, a member can write
|
|
147
|
+
# Vec<double, nFeatures> but the generated binding code is outside the
|
|
148
|
+
# class, so it needs Vec<double, GPCPatchDescriptor::nFeatures>.
|
|
149
|
+
# qualify_template_args then handles qualifying GPCPatchDescriptor to
|
|
150
|
+
# cv::optflow::GPCPatchDescriptor.
|
|
151
|
+
#
|
|
152
|
+
# Unscoped enum constants need the same treatment:
|
|
153
|
+
# FixedBuffer<int, Size>
|
|
154
|
+
# becomes
|
|
155
|
+
# FixedBuffer<int, Tests::EnumSized<N>::Size>
|
|
156
|
+
#
|
|
157
|
+
# Class templates need their template parameters preserved:
|
|
158
|
+
# FixedBuffer<int, Size>
|
|
159
|
+
# becomes
|
|
160
|
+
# FixedBuffer<int, Tests::StaticSized<N>::Size>
|
|
161
|
+
def qualify_class_static_members(spelling, class_cursor)
|
|
162
|
+
return spelling unless class_cursor
|
|
163
|
+
|
|
164
|
+
parent_kind = class_cursor.kind
|
|
165
|
+
return spelling unless parent_kind == :cursor_class_decl ||
|
|
166
|
+
parent_kind == :cursor_struct ||
|
|
167
|
+
parent_kind == :cursor_class_template
|
|
168
|
+
|
|
169
|
+
cache_key = class_cursor.usr
|
|
170
|
+
member_info = @class_static_members[cache_key] ||= begin
|
|
171
|
+
names = []
|
|
172
|
+
class_cursor.each(false) do |child|
|
|
173
|
+
if child.kind == :cursor_variable
|
|
174
|
+
names << child.spelling
|
|
175
|
+
elsif child.kind == :cursor_enum_decl && !child.enum_scoped?
|
|
176
|
+
child.each(false) do |enum_child|
|
|
177
|
+
names << enum_child.spelling if enum_child.kind == :cursor_enum_constant_decl
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
qualified_parent = if parent_kind == :cursor_class_template
|
|
182
|
+
qualified_display_name(class_cursor)
|
|
183
|
+
else
|
|
184
|
+
class_cursor.qualified_name
|
|
185
|
+
end
|
|
186
|
+
{ names: names, qualified_parent: qualified_parent }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
return spelling if member_info[:names].empty?
|
|
190
|
+
|
|
191
|
+
result = spelling.dup
|
|
192
|
+
qualified_parent = member_info[:qualified_parent]
|
|
193
|
+
display_name = class_cursor.display_name
|
|
194
|
+
member_info[:names].each do |name|
|
|
195
|
+
if display_name && !display_name.empty? && display_name != qualified_parent
|
|
196
|
+
partially_qualified = /(?<![:\w])#{Regexp.escape(display_name)}::#{Regexp.escape(name)}(?![:\w])/
|
|
197
|
+
result = result.gsub(partially_qualified, "#{qualified_parent}::#{name}")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
unqualified = /(?<![:\w])#{Regexp.escape(name)}(?![:\w])/
|
|
201
|
+
result = result.gsub(unqualified, "#{qualified_parent}::#{name}")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
result
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def preserve_template_parameter_names(spelling, template_cursor)
|
|
208
|
+
return spelling unless template_cursor&.kind == :cursor_class_template
|
|
209
|
+
|
|
210
|
+
result = spelling.dup
|
|
211
|
+
template_parameter_names(template_cursor).each do |name|
|
|
212
|
+
qualified_name = @type_index.qualified_name_for(name)
|
|
213
|
+
next if qualified_name.nil? || qualified_name == name
|
|
214
|
+
|
|
215
|
+
result = result.gsub(/(?<![:\w])#{Regexp.escape(qualified_name)}(?![:\w])/, name)
|
|
216
|
+
end
|
|
217
|
+
result
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
TEMPLATE_PARAMETER_KINDS = [:cursor_template_type_parameter,
|
|
223
|
+
:cursor_non_type_template_parameter,
|
|
224
|
+
:cursor_template_template_parameter].freeze
|
|
225
|
+
|
|
226
|
+
def type_spelling_pointer(type)
|
|
227
|
+
RubyBindgen::TypePointerFormatter.pointer_spelling(type) do |child_type|
|
|
228
|
+
type_spelling(child_type)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Qualify template arguments in a type spelling
|
|
233
|
+
# e.g., DataType<hfloat> -> DataType<cv::hfloat>
|
|
234
|
+
# Collects qualified names from both original and canonical types
|
|
235
|
+
def qualify_template_args(spelling, type, ignored_names: [])
|
|
236
|
+
return spelling if spelling.nil? || !spelling.include?('<')
|
|
237
|
+
|
|
238
|
+
qualifications = {}
|
|
239
|
+
if type
|
|
240
|
+
collect_type_qualifications(type, qualifications)
|
|
241
|
+
collect_type_qualifications(type.canonical, qualifications)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
spelling.scan(/(?<![:\w])([A-Z_a-z]\w*)(?!\w)/) do |match|
|
|
245
|
+
simple_name = match[0]
|
|
246
|
+
next if ignored_names.include?(simple_name)
|
|
247
|
+
next if qualifications.key?(simple_name)
|
|
248
|
+
|
|
249
|
+
qualified_name = @type_index.qualified_name_for(simple_name)
|
|
250
|
+
qualifications[simple_name] = qualified_name if qualified_name && simple_name != qualified_name
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
result = spelling.dup
|
|
254
|
+
qualifications.each do |simple_name, qualified_name|
|
|
255
|
+
next if simple_name == qualified_name
|
|
256
|
+
|
|
257
|
+
qualified_segments = qualified_name.split('::')
|
|
258
|
+
if qualified_segments.length > 2
|
|
259
|
+
(1...(qualified_segments.length - 1)).each do |index|
|
|
260
|
+
partial_name = qualified_segments[index..].join('::')
|
|
261
|
+
result = result.gsub(/(?<![:\w])#{Regexp.escape(partial_name)}(?![:\w])/, qualified_name)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
result = result.gsub(/(?<![:\w])#{Regexp.escape(simple_name)}(?!\w)/, qualified_name)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
result
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def template_parameter_names(cursor)
|
|
272
|
+
return [] unless cursor
|
|
273
|
+
|
|
274
|
+
cursor.find_by_kind(false, *TEMPLATE_PARAMETER_KINDS)
|
|
275
|
+
.map(&:spelling)
|
|
276
|
+
.reject(&:empty?)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def template_parameter_arguments(cursor)
|
|
280
|
+
return [] unless cursor
|
|
281
|
+
|
|
282
|
+
cursor.find_by_kind(false, *TEMPLATE_PARAMETER_KINDS)
|
|
283
|
+
.filter_map do |template_parameter|
|
|
284
|
+
name = template_parameter.spelling
|
|
285
|
+
next if name.empty?
|
|
286
|
+
|
|
287
|
+
declaration = template_parameter.extent.text
|
|
288
|
+
declaration&.match?(/\.\.\.\s*#{Regexp.escape(name)}\b/) ? "#{name}..." : name
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def qualify_template_parameter_packs(spelling, cursor)
|
|
293
|
+
return spelling unless cursor
|
|
294
|
+
|
|
295
|
+
template_cursor = if cursor.kind == :cursor_class_template
|
|
296
|
+
cursor
|
|
297
|
+
elsif cursor.semantic_parent&.kind == :cursor_class_template
|
|
298
|
+
cursor.semantic_parent
|
|
299
|
+
end
|
|
300
|
+
return spelling unless template_cursor
|
|
301
|
+
|
|
302
|
+
pack_names = template_parameter_arguments(template_cursor)
|
|
303
|
+
.select { |argument| argument.end_with?('...') }
|
|
304
|
+
.map { |argument| argument.delete_suffix('...') }
|
|
305
|
+
return spelling if pack_names.empty?
|
|
306
|
+
|
|
307
|
+
result = spelling.dup
|
|
308
|
+
pack_names.each do |name|
|
|
309
|
+
result = result.gsub(/(?<![:\w])#{Regexp.escape(name)}(?!\.\.\.|[:\w])/, "#{name}...")
|
|
310
|
+
end
|
|
311
|
+
result
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Recursively collect simple_name -> qualified_name mappings from a type's template arguments
|
|
315
|
+
def collect_type_qualifications(type, qualifications)
|
|
316
|
+
return if type.nil? || type.kind == :type_invalid
|
|
317
|
+
return unless type.num_template_arguments > 0
|
|
318
|
+
|
|
319
|
+
type.num_template_arguments.times do |i|
|
|
320
|
+
arg_type = type.template_argument_type(i)
|
|
321
|
+
next if arg_type.kind == :type_invalid
|
|
322
|
+
|
|
323
|
+
check_type = arg_type
|
|
324
|
+
check_type = check_type.pointee while check_type.kind == :type_pointer
|
|
325
|
+
|
|
326
|
+
decl = check_type.declaration
|
|
327
|
+
if decl.kind != :cursor_no_decl_found
|
|
328
|
+
simple_name = decl.spelling
|
|
329
|
+
if TEMPLATE_PARAMETER_KINDS.include?(decl.kind)
|
|
330
|
+
qualifications[simple_name] = simple_name unless simple_name.empty?
|
|
331
|
+
else
|
|
332
|
+
qualified_name = if decl.kind == :cursor_typedef_decl && decl.semantic_parent.kind == :cursor_class_template
|
|
333
|
+
"#{decl.semantic_parent.qualified_display_name}::#{simple_name}"
|
|
334
|
+
else
|
|
335
|
+
decl.qualified_name
|
|
336
|
+
end
|
|
337
|
+
if !simple_name.empty? && simple_name != qualified_name
|
|
338
|
+
qualifications[simple_name] = qualified_name
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
collect_type_qualifications(arg_type, qualifications)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Qualify dependent types within template arguments
|
|
348
|
+
# e.g., "cv::Point_<typename DataType<_Tp>::channel_type>"
|
|
349
|
+
# -> "cv::Point_<typename cv::DataType<_Tp>::channel_type>"
|
|
350
|
+
# Also handles nested template args such as:
|
|
351
|
+
# "typename Outer_<Outer_<T>>::type"
|
|
352
|
+
# -> "typename Tests::Outer_<Tests::Outer_<T>>::type"
|
|
353
|
+
#
|
|
354
|
+
# The source range APIs are not available here because libclang only gives us
|
|
355
|
+
# a type spelling string at this point, so we do a small balanced parse of the
|
|
356
|
+
# dependent base name and then qualify the nested template args recursively.
|
|
357
|
+
def qualify_dependent_types_in_template_args(spelling)
|
|
358
|
+
return spelling unless spelling.include?('typename')
|
|
359
|
+
|
|
360
|
+
result = String.new
|
|
361
|
+
index = 0
|
|
362
|
+
|
|
363
|
+
while (match = /\btypename\b/.match(spelling, index))
|
|
364
|
+
result << spelling[index...match.end(0)]
|
|
365
|
+
index = match.end(0)
|
|
366
|
+
|
|
367
|
+
while index < spelling.length && spelling[index].match?(/\s/)
|
|
368
|
+
result << spelling[index]
|
|
369
|
+
index += 1
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
identifier_match = /\A([A-Za-z_][A-Za-z0-9_]*)/.match(spelling[index..])
|
|
373
|
+
unless identifier_match
|
|
374
|
+
result << spelling[index]
|
|
375
|
+
index += 1
|
|
376
|
+
next
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
class_name = identifier_match[1]
|
|
380
|
+
name_end = index + class_name.length
|
|
381
|
+
template_part, after_template = balanced_template_suffix(spelling, name_end)
|
|
382
|
+
unless spelling[after_template, 2] == '::'
|
|
383
|
+
result << spelling[index...after_template]
|
|
384
|
+
index = after_template
|
|
385
|
+
next
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
qualified_name = @type_index.qualified_name_for(class_name) || class_name
|
|
389
|
+
qualified_template_part = if template_part.empty?
|
|
390
|
+
""
|
|
391
|
+
else
|
|
392
|
+
qualify_template_args(qualify_dependent_types_in_template_args(template_part), nil)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
result << "#{qualified_name}#{qualified_template_part}::"
|
|
396
|
+
index = after_template + 2
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
result << spelling[index..] if index < spelling.length
|
|
400
|
+
result
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def type_spelling_declared(type)
|
|
404
|
+
decl = type.declaration
|
|
405
|
+
|
|
406
|
+
case decl.kind
|
|
407
|
+
when :cursor_class_template
|
|
408
|
+
spelling = type.spelling
|
|
409
|
+
outer_name = spelling.sub(/\s*<.*/, '')
|
|
410
|
+
qualified = outer_name.match?(/\w+::/) ? spelling : spelling.sub(decl.spelling, decl.qualified_name)
|
|
411
|
+
qualify_template_args(qualify_dependent_types_in_template_args(qualified), type)
|
|
412
|
+
|
|
413
|
+
when :cursor_typedef_decl, :cursor_type_alias_decl
|
|
414
|
+
if decl.semantic_parent.kind == :cursor_class_template
|
|
415
|
+
const_prefix = type.const_qualified? ? "const " : ""
|
|
416
|
+
parent = decl.semantic_parent
|
|
417
|
+
display = qualified_display_name(parent)
|
|
418
|
+
qualified = parent.qualified_name
|
|
419
|
+
full_parent = if display.include?('<') && !display.start_with?(qualified)
|
|
420
|
+
"#{qualified}#{display[display.index('<')..]}"
|
|
421
|
+
else
|
|
422
|
+
display
|
|
423
|
+
end
|
|
424
|
+
"#{const_prefix}typename #{full_parent}::#{decl.spelling}"
|
|
425
|
+
else
|
|
426
|
+
type.fully_qualified_name(@printing_policy)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
when :cursor_no_decl_found
|
|
430
|
+
spelling = type.spelling
|
|
431
|
+
if spelling.include?('::')
|
|
432
|
+
# Preserve already-qualified public aliases such as std::exception_ptr
|
|
433
|
+
# instead of emitting canonical implementation-detail spellings.
|
|
434
|
+
qualify_template_args(qualify_dependent_types_in_template_args(spelling), type)
|
|
435
|
+
else
|
|
436
|
+
qualify_template_args(type.fully_qualified_name(@printing_policy), type)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
else
|
|
440
|
+
qualify_template_args(type.fully_qualified_name(@printing_policy), type)
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def type_spelling_unexposed(type)
|
|
445
|
+
decl = type.declaration
|
|
446
|
+
spelling = type.spelling
|
|
447
|
+
|
|
448
|
+
if decl.kind == :cursor_class_template && template_arguments_need_type_spelling?(type)
|
|
449
|
+
arg_spellings = (0...type.num_template_arguments).map do |index|
|
|
450
|
+
type_spelling(type.template_argument_type(index))
|
|
451
|
+
end
|
|
452
|
+
const_prefix = type.const_qualified? ? "const " : ""
|
|
453
|
+
return "#{const_prefix}#{decl.qualified_name}<#{arg_spellings.join(', ')}>"
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
if decl.kind == :cursor_no_decl_found && spelling.include?('::')
|
|
457
|
+
qualify_template_args(qualify_dependent_types_in_template_args(spelling), type)
|
|
458
|
+
else
|
|
459
|
+
qualify_template_args(qualify_dependent_types_in_template_args(type.fully_qualified_name(@printing_policy)), type)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def template_arguments_need_type_spelling?(type)
|
|
464
|
+
return false unless type.num_template_arguments > 0
|
|
465
|
+
|
|
466
|
+
type.num_template_arguments.times.any? do |index|
|
|
467
|
+
template_argument_needs_type_spelling?(type.template_argument_type(index))
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def template_argument_needs_type_spelling?(type)
|
|
472
|
+
return false if type.nil? || type.kind == :type_invalid
|
|
473
|
+
|
|
474
|
+
case type.kind
|
|
475
|
+
when :type_typedef, :type_elaborated
|
|
476
|
+
# LLVM 21 can still surface dependent alias/tag args as :type_elaborated.
|
|
477
|
+
# LLVM 22 usually surfaces the same cases as bare :type_typedef.
|
|
478
|
+
true
|
|
479
|
+
when :type_pointer
|
|
480
|
+
template_argument_needs_type_spelling?(type.pointee)
|
|
481
|
+
when :type_lvalue_ref, :type_rvalue_ref
|
|
482
|
+
template_argument_needs_type_spelling?(type.non_reference_type)
|
|
483
|
+
else
|
|
484
|
+
template_arguments_need_type_spelling?(type)
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def balanced_template_suffix(text, start_index)
|
|
489
|
+
return ["", start_index] unless text[start_index] == '<'
|
|
490
|
+
|
|
491
|
+
depth = 0
|
|
492
|
+
index = start_index
|
|
493
|
+
while index < text.length
|
|
494
|
+
case text[index]
|
|
495
|
+
when '<'
|
|
496
|
+
depth += 1
|
|
497
|
+
when '>'
|
|
498
|
+
depth -= 1
|
|
499
|
+
return [text[start_index..index], index + 1] if depth == 0
|
|
500
|
+
end
|
|
501
|
+
index += 1
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
["", start_index]
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<%- if under -%>
|
|
2
|
+
Rice::Data_Type<<%= cpp_type %>> <%= cursor.cruby_name %> = define_class_under<<%= cpp_type %>>(<%= under.cruby_name %>, "<%= ruby_name %>")<%= children %>
|
|
3
|
+
<%- else -%>
|
|
4
|
+
Rice::Data_Type<<%= cpp_type %>> <%= cursor.cruby_name %> = define_class<<%= cpp_type %>>("<%= ruby_name %>")<%= children %>
|
|
5
|
+
<%- end -%>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<%- if cursor.semantic_parent.kind == :cursor_translation_unit || cursor.semantic_parent.kind == :cursor_namespace -%>
|
|
2
|
+
<%= cursor.semantic_parent.cruby_name %>.define_singleton_attr("<%= cursor.ruby_name %>", &<%= cursor.qualified_name %>);
|
|
3
|
+
<%- else -%>
|
|
4
|
+
define_singleton_attr("<%= cursor.ruby_name %>", &<%= qualified_parent %>::<%= cursor.spelling %>)
|
|
5
|
+
<%- end -%>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'find'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
module RubyBindgen
|
|
7
|
+
class Inputter
|
|
8
|
+
include Enumerable
|
|
9
|
+
attr_reader :base_path, :globs, :exclude_glob
|
|
10
|
+
|
|
11
|
+
def initialize(base_path, globs=nil, exclude_glob=[])
|
|
12
|
+
@base_path = base_path
|
|
13
|
+
@globs = Array(globs).empty? ? ["**/*.{h,hpp}"] : Array(globs)
|
|
14
|
+
@exclude_glob = exclude_glob
|
|
15
|
+
|
|
16
|
+
if RUBY_PLATFORM.match?(/mswin/) || RUBY_PLATFORM.match?(/mingw/)
|
|
17
|
+
@base_path = @base_path.gsub('\\', '/')
|
|
18
|
+
@globs = @globs.map { |g| g.gsub('\\', '/') }
|
|
19
|
+
@exclude_glob = @exclude_glob.map { |g| g.gsub('\\', '/') }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each
|
|
24
|
+
raise(ArgumentError, "No block given") unless block_given?
|
|
25
|
+
|
|
26
|
+
yielded = 0
|
|
27
|
+
seen = Set.new
|
|
28
|
+
self.globs.each do |glob|
|
|
29
|
+
search = File.join(self.base_path, glob)
|
|
30
|
+
Dir.glob(search).each do |path|
|
|
31
|
+
next if exclude.include?(path)
|
|
32
|
+
next unless seen.add?(path)
|
|
33
|
+
|
|
34
|
+
yielded += 1
|
|
35
|
+
relative_path = Pathname.new(path).relative_path_from(self.base_path)
|
|
36
|
+
yield path, relative_path.to_path
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Silent empty output is one of the most common config mistakes. Warn
|
|
41
|
+
# so a typo'd match: glob doesn't look like a successful run.
|
|
42
|
+
if yielded.zero?
|
|
43
|
+
warn "ruby-bindgen: no input files matched #{self.globs.inspect} under #{self.base_path}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def exclude
|
|
48
|
+
@exclude ||= self.exclude_glob.map do |exclude|
|
|
49
|
+
search = File.join(self.base_path, exclude)
|
|
50
|
+
Dir.glob(search)
|
|
51
|
+
end.flatten.uniq
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|