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,495 @@
|
|
|
1
|
+
module RubyBindgen
|
|
2
|
+
module Generators
|
|
3
|
+
class Rice
|
|
4
|
+
# Qualifies source-written references using libclang spans for location and
|
|
5
|
+
# cursor metadata for the replacement text. This preserves the original
|
|
6
|
+
# written expression everywhere except the exact references that need
|
|
7
|
+
# namespace or class qualification once emitted outside their source scope.
|
|
8
|
+
class ReferenceQualifier
|
|
9
|
+
# Qualify names inside source-written text without trusting the source
|
|
10
|
+
# range text itself for replacement content.
|
|
11
|
+
#
|
|
12
|
+
# Examples:
|
|
13
|
+
# text = 'helper("helper")'
|
|
14
|
+
# => 'quoted::helper("helper")'
|
|
15
|
+
#
|
|
16
|
+
# text = 'Holder<Tag>::value'
|
|
17
|
+
# => 'QualifiedDefaults::Holder<QualifiedDefaults::Tag>::value'
|
|
18
|
+
def qualify_source_references(root_cursor, source_text, source_text_offset, qualify_decl_refs: true, substitutions: {})
|
|
19
|
+
range_replacements = []
|
|
20
|
+
type_fallbacks = []
|
|
21
|
+
|
|
22
|
+
root_cursor.find_by_kind(true, :cursor_type_ref, :cursor_template_ref) do |type_ref|
|
|
23
|
+
ref = type_ref.referenced
|
|
24
|
+
next unless ref && ref.kind != :cursor_invalid_file
|
|
25
|
+
|
|
26
|
+
simple_name = ref.spelling
|
|
27
|
+
if simple_name && substitutions.key?(simple_name)
|
|
28
|
+
range = type_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece])
|
|
29
|
+
replacement = source_range_replacement(source_text, source_text_offset, range, substitutions[simple_name])
|
|
30
|
+
if replacement
|
|
31
|
+
range_replacements << replacement.merge(kind: :type)
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Template type parameters stay visible by bare name in generated
|
|
37
|
+
# template code, so they should not be qualified here.
|
|
38
|
+
next if [:cursor_template_type_parameter, :cursor_template_template_parameter].include?(ref.kind)
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
is_dependent_typedef = ref.kind == :cursor_typedef_decl && ref.semantic_parent.kind == :cursor_class_template
|
|
42
|
+
qualified_name = if is_dependent_typedef
|
|
43
|
+
"#{ref.semantic_parent.qualified_display_name}::#{ref.spelling}"
|
|
44
|
+
else
|
|
45
|
+
ref.qualified_name
|
|
46
|
+
end
|
|
47
|
+
simple_name = ref.spelling
|
|
48
|
+
next if simple_name.nil? || simple_name.empty?
|
|
49
|
+
next if simple_name == qualified_name
|
|
50
|
+
|
|
51
|
+
range = type_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece])
|
|
52
|
+
replacement = source_range_replacement(source_text, source_text_offset, range)
|
|
53
|
+
if replacement
|
|
54
|
+
start_index = replacement[:start_offset] - source_text_offset
|
|
55
|
+
end_index = replacement[:end_offset] - source_text_offset
|
|
56
|
+
start_index = expand_name_start_to_qualifier(source_text, start_index)
|
|
57
|
+
replacement = replacement.merge(start_offset: source_text_offset + start_index)
|
|
58
|
+
span_text = source_text.byteslice(start_index, end_index - start_index)
|
|
59
|
+
trailing_scope = source_text.byteslice(end_index, 2).to_s == '::'
|
|
60
|
+
|
|
61
|
+
replacement_name = if ref.kind == :cursor_class_template && trailing_scope && !span_text.include?('<')
|
|
62
|
+
ref.qualified_display_name
|
|
63
|
+
else
|
|
64
|
+
qualified_name
|
|
65
|
+
end
|
|
66
|
+
replacement_text = replacement_from_name_span(span_text, simple_name, replacement_name)
|
|
67
|
+
if is_dependent_typedef && replacement_text && !trailing_scope &&
|
|
68
|
+
!preceded_by_typename?(source_text, start_index)
|
|
69
|
+
replacement_text = "typename #{replacement_text}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if replacement_text && replacement_text != span_text
|
|
73
|
+
range_replacements << replacement.merge(replacement: replacement_text, kind: :type)
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
type_fallbacks << [ref, simple_name, qualified_name, is_dependent_typedef]
|
|
79
|
+
rescue ArgumentError
|
|
80
|
+
# Skip if we can't get qualified name (e.g., invalid cursor)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
decl_fallbacks = []
|
|
85
|
+
if qualify_decl_refs
|
|
86
|
+
decl_refs = root_cursor.find_by_kind(true, :cursor_decl_ref_expr).to_a
|
|
87
|
+
decl_refs = [root_cursor] + decl_refs if root_cursor.kind == :cursor_decl_ref_expr
|
|
88
|
+
decl_refs.each do |decl_ref|
|
|
89
|
+
ref = decl_ref.referenced
|
|
90
|
+
next unless ref && ref.kind != :cursor_invalid_file
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
simple_name = ref.spelling
|
|
94
|
+
next if simple_name.nil? || simple_name.empty?
|
|
95
|
+
|
|
96
|
+
if substitutions.key?(simple_name)
|
|
97
|
+
range = if decl_ref.extent.text.include?('<')
|
|
98
|
+
decl_ref.spelling_name_range(0)
|
|
99
|
+
else
|
|
100
|
+
decl_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece])
|
|
101
|
+
end
|
|
102
|
+
replacement = source_range_replacement(source_text, source_text_offset, range, substitutions[simple_name])
|
|
103
|
+
if replacement
|
|
104
|
+
range_replacements << replacement.merge(kind: :decl)
|
|
105
|
+
next
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
next if ref.kind == :cursor_non_type_template_parameter
|
|
110
|
+
|
|
111
|
+
if ref.kind == :cursor_cxx_method
|
|
112
|
+
next if source_text.match?(/::#{Regexp.escape(simple_name)}\s*\(/)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
qualified_name = if ref.kind == :cursor_enum_constant_decl &&
|
|
116
|
+
ref.semantic_parent.kind == :cursor_enum_decl &&
|
|
117
|
+
!ref.semantic_parent.enum_scoped?
|
|
118
|
+
enum_parent = ref.semantic_parent.semantic_parent
|
|
119
|
+
if enum_parent && enum_parent.kind == :cursor_namespace
|
|
120
|
+
"#{enum_parent.qualified_name}::#{simple_name}"
|
|
121
|
+
elsif ref.semantic_parent.anonymous? &&
|
|
122
|
+
enum_parent && enum_parent.kind != :cursor_translation_unit
|
|
123
|
+
"#{enum_parent.qualified_name}::#{simple_name}"
|
|
124
|
+
else
|
|
125
|
+
ref.qualified_name
|
|
126
|
+
end
|
|
127
|
+
elsif ref.semantic_parent.kind == :cursor_class_template
|
|
128
|
+
"#{ref.semantic_parent.qualified_display_name}::#{simple_name}"
|
|
129
|
+
else
|
|
130
|
+
ref.qualified_name
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
next if simple_name == qualified_name
|
|
134
|
+
next if qualified_name.start_with?('::')
|
|
135
|
+
next unless qualified_name.end_with?(simple_name)
|
|
136
|
+
|
|
137
|
+
range = if decl_ref.extent.text.include?('<')
|
|
138
|
+
decl_ref.spelling_name_range(0)
|
|
139
|
+
else
|
|
140
|
+
decl_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece])
|
|
141
|
+
end
|
|
142
|
+
replacement = source_range_replacement(source_text, source_text_offset, range)
|
|
143
|
+
if replacement
|
|
144
|
+
start_index = replacement[:start_offset] - source_text_offset
|
|
145
|
+
end_index = replacement[:end_offset] - source_text_offset
|
|
146
|
+
span_text = source_text.byteslice(start_index, end_index - start_index)
|
|
147
|
+
replacement_text = replacement_from_name_span(span_text, simple_name, qualified_name)
|
|
148
|
+
if replacement_text && replacement_text != span_text
|
|
149
|
+
range_replacements << replacement.merge(replacement: replacement_text, kind: :decl)
|
|
150
|
+
next
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
decl_fallbacks << [simple_name, qualified_name]
|
|
155
|
+
rescue ArgumentError
|
|
156
|
+
# Skip if we can't get qualified name
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
decl_replacements = range_replacements.select { |replacement| replacement[:kind] == :decl }
|
|
161
|
+
range_replacements.reject! do |replacement|
|
|
162
|
+
replacement[:kind] == :type &&
|
|
163
|
+
decl_replacements.any? do |decl_replacement|
|
|
164
|
+
decl_replacement[:start_offset] <= replacement[:start_offset] &&
|
|
165
|
+
decl_replacement[:end_offset] >= replacement[:end_offset]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
range_replacements = collapse_same_start_replacements(range_replacements)
|
|
171
|
+
|
|
172
|
+
source_text = apply_source_replacements(source_text, source_text_offset, range_replacements) unless range_replacements.empty?
|
|
173
|
+
|
|
174
|
+
type_fallbacks.each do |type_fallback|
|
|
175
|
+
ref, simple_name, qualified_name, is_dependent_typedef = type_fallback
|
|
176
|
+
source_text = fallback_qualify_type_reference(source_text, ref, simple_name, qualified_name, is_dependent_typedef)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
decl_fallbacks.each do |decl_fallback|
|
|
180
|
+
simple_name, qualified_name = decl_fallback
|
|
181
|
+
source_text = fallback_qualify_declaration_reference(source_text, simple_name, qualified_name)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
source_text
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Expand the written name span to the desired fully qualified form
|
|
188
|
+
# without dropping template args or clobbering existing qualifiers.
|
|
189
|
+
#
|
|
190
|
+
# Examples:
|
|
191
|
+
# span_text = 'helper'
|
|
192
|
+
# simple_name = 'helper'
|
|
193
|
+
# qualified_name = 'quoted::helper'
|
|
194
|
+
# => 'quoted::helper'
|
|
195
|
+
#
|
|
196
|
+
# span_text = 'makePtr<inner::IndexParams>'
|
|
197
|
+
# simple_name = 'makePtr'
|
|
198
|
+
# qualified_name = 'outer::makePtr'
|
|
199
|
+
# => 'outer::makePtr<inner::IndexParams>'
|
|
200
|
+
#
|
|
201
|
+
# span_text = 'PerfLevel::SLOW'
|
|
202
|
+
# simple_name = 'SLOW'
|
|
203
|
+
# qualified_name = 'multiline::PerfLevel::SLOW'
|
|
204
|
+
# => 'multiline::PerfLevel::SLOW'
|
|
205
|
+
def replacement_from_name_span(span_text, simple_name, qualified_name)
|
|
206
|
+
return qualified_name if span_text == simple_name
|
|
207
|
+
return qualified_name if span_text.include?('::') && span_text.end_with?(simple_name)
|
|
208
|
+
return span_text.sub(/\A#{Regexp.escape(simple_name)}/, qualified_name) if span_text.start_with?(simple_name)
|
|
209
|
+
|
|
210
|
+
nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Find the byte offset of the declaration's top-level default-value `=`.
|
|
214
|
+
#
|
|
215
|
+
# Examples:
|
|
216
|
+
# 'FILE* stream = stdout'
|
|
217
|
+
# => offset of the `=` before stdout
|
|
218
|
+
#
|
|
219
|
+
# 'template<typename U = int> class Container = Box'
|
|
220
|
+
# => offset of the `=` before Box, not the inner `= int`
|
|
221
|
+
#
|
|
222
|
+
# Nested delimiters are skipped so `=` inside template parameter lists,
|
|
223
|
+
# function types, arrays, and braced expressions does not get mistaken for
|
|
224
|
+
# the declaration's own default separator.
|
|
225
|
+
def top_level_default_separator_offset(text)
|
|
226
|
+
return nil if text.nil? || text.empty?
|
|
227
|
+
|
|
228
|
+
angle_depth = 0
|
|
229
|
+
paren_depth = 0
|
|
230
|
+
bracket_depth = 0
|
|
231
|
+
brace_depth = 0
|
|
232
|
+
in_single_quote = false
|
|
233
|
+
in_double_quote = false
|
|
234
|
+
escaped = false
|
|
235
|
+
byte_offset = 0
|
|
236
|
+
|
|
237
|
+
text.each_char do |char|
|
|
238
|
+
if in_single_quote || in_double_quote
|
|
239
|
+
if escaped
|
|
240
|
+
escaped = false
|
|
241
|
+
elsif char == '\\'
|
|
242
|
+
escaped = true
|
|
243
|
+
elsif in_single_quote && char == "'"
|
|
244
|
+
in_single_quote = false
|
|
245
|
+
elsif in_double_quote && char == '"'
|
|
246
|
+
in_double_quote = false
|
|
247
|
+
end
|
|
248
|
+
else
|
|
249
|
+
case char
|
|
250
|
+
when "'"
|
|
251
|
+
in_single_quote = true
|
|
252
|
+
when '"'
|
|
253
|
+
in_double_quote = true
|
|
254
|
+
when '<'
|
|
255
|
+
angle_depth += 1
|
|
256
|
+
when '>'
|
|
257
|
+
angle_depth -= 1 if angle_depth > 0
|
|
258
|
+
when '('
|
|
259
|
+
paren_depth += 1
|
|
260
|
+
when ')'
|
|
261
|
+
paren_depth -= 1 if paren_depth > 0
|
|
262
|
+
when '['
|
|
263
|
+
bracket_depth += 1
|
|
264
|
+
when ']'
|
|
265
|
+
bracket_depth -= 1 if bracket_depth > 0
|
|
266
|
+
when '{'
|
|
267
|
+
brace_depth += 1
|
|
268
|
+
when '}'
|
|
269
|
+
brace_depth -= 1 if brace_depth > 0
|
|
270
|
+
when '='
|
|
271
|
+
if angle_depth.zero? && paren_depth.zero? && bracket_depth.zero? && brace_depth.zero?
|
|
272
|
+
return byte_offset
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
byte_offset += char.bytesize
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
nil
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Split a declaration at its top-level default-value '=' and return both
|
|
284
|
+
# the written default text and its byte offset in the source file.
|
|
285
|
+
#
|
|
286
|
+
# Examples:
|
|
287
|
+
# 'FILE* stream = stdout'
|
|
288
|
+
# => ['stdout', <offset of the s in stdout>]
|
|
289
|
+
#
|
|
290
|
+
# "PerfLevel level\n = PerfLevel::SLOW"
|
|
291
|
+
# => ['PerfLevel::SLOW', <offset of the P in PerfLevel::SLOW>]
|
|
292
|
+
#
|
|
293
|
+
# 'typename U = Box<Tag>'
|
|
294
|
+
# => ['Box<Tag>', <offset of the B in Box<Tag>>]
|
|
295
|
+
#
|
|
296
|
+
# 'template<typename U = int> class Container = Box'
|
|
297
|
+
# => ['Box', <offset of the B in Box>]
|
|
298
|
+
#
|
|
299
|
+
# This is text extraction only. Qualification happens later using cursor
|
|
300
|
+
# information so we do not lose semantic information for either function
|
|
301
|
+
# defaults or template parameter defaults.
|
|
302
|
+
def extract_default_text(param)
|
|
303
|
+
param_extent = param.extent.text
|
|
304
|
+
return nil unless param_extent
|
|
305
|
+
|
|
306
|
+
separator_offset = top_level_default_separator_offset(param_extent)
|
|
307
|
+
return nil unless separator_offset
|
|
308
|
+
|
|
309
|
+
before = param_extent.byteslice(0, separator_offset)
|
|
310
|
+
after = param_extent.byteslice((separator_offset + 1)..-1).to_s
|
|
311
|
+
|
|
312
|
+
leading_whitespace = after[/\A\s*/] || ""
|
|
313
|
+
default_text = after.delete_prefix(leading_whitespace)
|
|
314
|
+
return nil if default_text.empty?
|
|
315
|
+
|
|
316
|
+
default_text_offset = param.extent.start.offset + before.bytesize + 1 + leading_whitespace.bytesize
|
|
317
|
+
[default_text, default_text_offset]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
private
|
|
321
|
+
|
|
322
|
+
# Convert a libclang source range into offsets relative to the extracted
|
|
323
|
+
# default-expression text so semantic replacements can be applied safely.
|
|
324
|
+
#
|
|
325
|
+
# Example:
|
|
326
|
+
# text = 'makePtr<inner::IndexParams>()'
|
|
327
|
+
# base_offset = 1200
|
|
328
|
+
# range = source range for 'makePtr<inner::IndexParams>'
|
|
329
|
+
#
|
|
330
|
+
# Returns offsets relative to the file:
|
|
331
|
+
# { start_offset: 1200, end_offset: 1228, replacement: ... }
|
|
332
|
+
#
|
|
333
|
+
# Those offsets are later converted back into indexes relative to `text`
|
|
334
|
+
# before patching only that exact span.
|
|
335
|
+
def source_range_replacement(text, base_offset, range, replacement = nil)
|
|
336
|
+
return nil if range.nil? || range.null?
|
|
337
|
+
|
|
338
|
+
start_offset = range.start.offset
|
|
339
|
+
end_offset = range.end.offset
|
|
340
|
+
text_end_offset = base_offset + text.bytesize
|
|
341
|
+
return nil if start_offset < base_offset || end_offset > text_end_offset || end_offset < start_offset
|
|
342
|
+
|
|
343
|
+
{ start_offset: start_offset, end_offset: end_offset, replacement: replacement }
|
|
344
|
+
rescue ArgumentError
|
|
345
|
+
nil
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Apply non-overlapping source replacements from right to left so earlier
|
|
349
|
+
# offsets remain valid while rewriting a default expression.
|
|
350
|
+
#
|
|
351
|
+
# Example:
|
|
352
|
+
# text = 'helper("helper")'
|
|
353
|
+
# replacements = [{start_offset: ..., end_offset: ..., replacement: 'quoted::helper'}]
|
|
354
|
+
#
|
|
355
|
+
# Produces:
|
|
356
|
+
# 'quoted::helper("helper")'
|
|
357
|
+
#
|
|
358
|
+
# The string literal stays untouched because only the decl-ref span is replaced.
|
|
359
|
+
def apply_source_replacements(text, base_offset, replacements)
|
|
360
|
+
replacements
|
|
361
|
+
.uniq { |r| [r[:start_offset], r[:end_offset], r[:replacement]] }
|
|
362
|
+
.sort_by { |r| -r[:start_offset] }
|
|
363
|
+
.reduce(text.dup) do |result, replacement|
|
|
364
|
+
start_index = replacement[:start_offset] - base_offset
|
|
365
|
+
end_index = replacement[:end_offset] - base_offset
|
|
366
|
+
result.byteslice(0, start_index) +
|
|
367
|
+
replacement[:replacement] +
|
|
368
|
+
result.byteslice(end_index..-1).to_s
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# A nested type reference can expand its start backward over the owning
|
|
373
|
+
# qualifier chain, producing a second replacement that starts at the same
|
|
374
|
+
# byte offset as the outer type reference but covers more text:
|
|
375
|
+
# OriginalClassName
|
|
376
|
+
# OriginalClassName::Params
|
|
377
|
+
# Keeping both corrupts the final source text, so prefer the broader span
|
|
378
|
+
# at a shared start offset.
|
|
379
|
+
def collapse_same_start_replacements(replacements)
|
|
380
|
+
replacements
|
|
381
|
+
.group_by { |replacement| replacement[:start_offset] }
|
|
382
|
+
.values
|
|
383
|
+
.map do |group|
|
|
384
|
+
group.max_by do |replacement|
|
|
385
|
+
kind_weight = replacement[:kind] == :decl ? 1 : 0
|
|
386
|
+
span_width = replacement[:end_offset] - replacement[:start_offset]
|
|
387
|
+
[kind_weight, span_width]
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Check whether the source text immediately before a replacement span already
|
|
393
|
+
# ends with `typename`, so we do not emit `typename typename Foo::Bar`.
|
|
394
|
+
#
|
|
395
|
+
# Examples:
|
|
396
|
+
# text = 'typename SearchIndex<Distance>::ElementType()'
|
|
397
|
+
# start_index = index of the S in SearchIndex
|
|
398
|
+
# => true
|
|
399
|
+
#
|
|
400
|
+
# text = 'SearchIndex<Distance>::ElementType()'
|
|
401
|
+
# start_index = index of the S in SearchIndex
|
|
402
|
+
# => false
|
|
403
|
+
def preceded_by_typename?(text, start_index)
|
|
404
|
+
text.byteslice(0, start_index).to_s.match?(/(?:\A|[^\w:])typename\s+\z/)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Extend a name-token span backward to include a written qualifier chain.
|
|
408
|
+
#
|
|
409
|
+
# Examples:
|
|
410
|
+
# text = 'makePtr<inner::IndexParams>()'
|
|
411
|
+
# start_index = index of the I in IndexParams
|
|
412
|
+
# => index of the i in inner
|
|
413
|
+
#
|
|
414
|
+
# text = 'SearchIndex<Distance>::ElementType()'
|
|
415
|
+
# start_index = index of the E in ElementType
|
|
416
|
+
# => index of the S in SearchIndex
|
|
417
|
+
def expand_name_start_to_qualifier(text, start_index)
|
|
418
|
+
index = start_index
|
|
419
|
+
|
|
420
|
+
loop do
|
|
421
|
+
break unless index >= 2 && text.byteslice(index - 2, 2) == '::'
|
|
422
|
+
|
|
423
|
+
segment_end = index - 2
|
|
424
|
+
cursor = segment_end - 1
|
|
425
|
+
break if cursor.negative?
|
|
426
|
+
|
|
427
|
+
if text[cursor] == '>'
|
|
428
|
+
depth = 0
|
|
429
|
+
while cursor >= 0
|
|
430
|
+
case text[cursor]
|
|
431
|
+
when '>'
|
|
432
|
+
depth += 1
|
|
433
|
+
when '<'
|
|
434
|
+
depth -= 1
|
|
435
|
+
break if depth.zero?
|
|
436
|
+
end
|
|
437
|
+
cursor -= 1
|
|
438
|
+
end
|
|
439
|
+
break if cursor.negative?
|
|
440
|
+
cursor -= 1
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
while cursor >= 0 && text[cursor].match?(/[A-Za-z0-9_]/)
|
|
444
|
+
cursor -= 1
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
segment_start = cursor + 1
|
|
448
|
+
break if segment_start == segment_end
|
|
449
|
+
|
|
450
|
+
index = segment_start
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
index
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def fallback_qualify_type_reference(text, ref, simple_name, qualified_name, is_dependent_typedef)
|
|
457
|
+
# Replace unqualified occurrences (negative lookbehind avoids already-qualified names)
|
|
458
|
+
result = text.gsub(/(?<!::)\b#{Regexp.escape(simple_name)}\b/, qualified_name)
|
|
459
|
+
|
|
460
|
+
# Replace partially-qualified names (e.g., flann::Foo -> cv::flann::Foo)
|
|
461
|
+
# Match any prefix::simple_name that isn't already fully qualified
|
|
462
|
+
result = result.gsub(/(?<!\w)(\w+(?:::\w+)*)::#{Regexp.escape(simple_name)}\b/) do |match|
|
|
463
|
+
match == qualified_name ? match : qualified_name
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# For class template refs used as qualifiers (before ::) without explicit template args,
|
|
467
|
+
# insert template parameters (e.g., CompositeIndex:: -> CompositeIndex<Distance>::)
|
|
468
|
+
if ref.kind == :cursor_class_template
|
|
469
|
+
display_name = ref.qualified_display_name
|
|
470
|
+
result = result.gsub(/#{Regexp.escape(qualified_name)}(?=\s*::)/, display_name)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Add 'typename' for dependent typedef names used as types (not as qualifiers before ::)
|
|
474
|
+
# e.g., SearchIndex<Distance>::ElementType() needs typename, but Vec3Type::all() does not
|
|
475
|
+
if is_dependent_typedef
|
|
476
|
+
result = result.gsub(/(?<!typename )#{Regexp.escape(qualified_name)}(?!\s*::)/, "typename #{qualified_name}")
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
result
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def fallback_qualify_declaration_reference(text, simple_name, qualified_name)
|
|
483
|
+
# Apply qualification (negative lookbehind avoids double-qualifying)
|
|
484
|
+
result = text.gsub(/(?<!::)\b#{Regexp.escape(simple_name)}\b/, qualified_name)
|
|
485
|
+
|
|
486
|
+
# Replace partially-qualified names (e.g., fisheye::CALIB_FIX_INTRINSIC -> cv::fisheye::CALIB_FIX_INTRINSIC)
|
|
487
|
+
# Match any prefix::simple_name that isn't already fully qualified
|
|
488
|
+
result.gsub(/(?<!\w)(\w+(?:::\w+)*)::#{Regexp.escape(simple_name)}\b/) do |match|
|
|
489
|
+
match == qualified_name ? match : qualified_name
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
end
|