Class: RubyBindgen::Generators::Rice::ReferenceQualifier
- Inherits:
-
Object
- Object
- RubyBindgen::Generators::Rice::ReferenceQualifier
- Defined in:
- lib/ruby-bindgen/generators/rice/reference_qualifier.rb
Overview
Qualifies source-written references using libclang spans for location and cursor metadata for the replacement text. This preserves the original written expression everywhere except the exact references that need namespace or class qualification once emitted outside their source scope.
Instance Method Summary collapse
-
#extract_default_text(param) ⇒ Object
Split a declaration at its top-level default-value ‘=’ and return both the written default text and its byte offset in the source file.
-
#qualify_source_references(root_cursor, source_text, source_text_offset, qualify_decl_refs: true, substitutions: {}) ⇒ Object
Qualify names inside source-written text without trusting the source range text itself for replacement content.
-
#replacement_from_name_span(span_text, simple_name, qualified_name) ⇒ Object
Expand the written name span to the desired fully qualified form without dropping template args or clobbering existing qualifiers.
-
#top_level_default_separator_offset(text) ⇒ Object
Find the byte offset of the declaration’s top-level default-value ‘=`.
Instance Method Details
#extract_default_text(param) ⇒ Object
Split a declaration at its top-level default-value ‘=’ and return both the written default text and its byte offset in the source file.
Examples:
'FILE* stream = stdout'
=> ['stdout', <offset of the s in stdout>]
"PerfLevel level\n = PerfLevel::SLOW"
=> ['PerfLevel::SLOW', <offset of the P in PerfLevel::SLOW>]
'typename U = Box<Tag>'
=> ['Box<Tag>', <offset of the B in Box<Tag>>]
'template<typename U = int> class Container = Box'
=> ['Box', <offset of the B in Box>]
This is text extraction only. Qualification happens later using cursor information so we do not lose semantic information for either function defaults or template parameter defaults.
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/ruby-bindgen/generators/rice/reference_qualifier.rb', line 302 def extract_default_text(param) param_extent = param.extent.text return nil unless param_extent separator_offset = top_level_default_separator_offset(param_extent) return nil unless separator_offset before = param_extent.byteslice(0, separator_offset) after = param_extent.byteslice((separator_offset + 1)..-1).to_s leading_whitespace = after[/\A\s*/] || "" default_text = after.delete_prefix(leading_whitespace) return nil if default_text.empty? default_text_offset = param.extent.start.offset + before.bytesize + 1 + leading_whitespace.bytesize [default_text, default_text_offset] end |
#qualify_source_references(root_cursor, source_text, source_text_offset, qualify_decl_refs: true, substitutions: {}) ⇒ Object
Qualify names inside source-written text without trusting the source range text itself for replacement content.
Examples:
text = 'helper("helper")'
=> 'quoted::helper("helper")'
text = 'Holder<Tag>::value'
=> 'QualifiedDefaults::Holder<QualifiedDefaults::Tag>::value'
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/ruby-bindgen/generators/rice/reference_qualifier.rb', line 18 def qualify_source_references(root_cursor, source_text, source_text_offset, qualify_decl_refs: true, substitutions: {}) range_replacements = [] type_fallbacks = [] root_cursor.find_by_kind(true, :cursor_type_ref, :cursor_template_ref) do |type_ref| ref = type_ref.referenced next unless ref && ref.kind != :cursor_invalid_file simple_name = ref.spelling if simple_name && substitutions.key?(simple_name) range = type_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece]) replacement = source_range_replacement(source_text, source_text_offset, range, substitutions[simple_name]) if replacement range_replacements << replacement.merge(kind: :type) next end end # Template type parameters stay visible by bare name in generated # template code, so they should not be qualified here. next if [:cursor_template_type_parameter, :cursor_template_template_parameter].include?(ref.kind) begin is_dependent_typedef = ref.kind == :cursor_typedef_decl && ref.semantic_parent.kind == :cursor_class_template qualified_name = if is_dependent_typedef "#{ref.semantic_parent.qualified_display_name}::#{ref.spelling}" else ref.qualified_name end simple_name = ref.spelling next if simple_name.nil? || simple_name.empty? next if simple_name == qualified_name range = type_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece]) replacement = source_range_replacement(source_text, source_text_offset, range) if replacement start_index = replacement[:start_offset] - source_text_offset end_index = replacement[:end_offset] - source_text_offset start_index = (source_text, start_index) replacement = replacement.merge(start_offset: source_text_offset + start_index) span_text = source_text.byteslice(start_index, end_index - start_index) trailing_scope = source_text.byteslice(end_index, 2).to_s == '::' replacement_name = if ref.kind == :cursor_class_template && trailing_scope && !span_text.include?('<') ref.qualified_display_name else qualified_name end replacement_text = replacement_from_name_span(span_text, simple_name, replacement_name) if is_dependent_typedef && replacement_text && !trailing_scope && !preceded_by_typename?(source_text, start_index) replacement_text = "typename #{replacement_text}" end if replacement_text && replacement_text != span_text range_replacements << replacement.merge(replacement: replacement_text, kind: :type) next end end type_fallbacks << [ref, simple_name, qualified_name, is_dependent_typedef] rescue ArgumentError # Skip if we can't get qualified name (e.g., invalid cursor) end end decl_fallbacks = [] if qualify_decl_refs decl_refs = root_cursor.find_by_kind(true, :cursor_decl_ref_expr).to_a decl_refs = [root_cursor] + decl_refs if root_cursor.kind == :cursor_decl_ref_expr decl_refs.each do |decl_ref| ref = decl_ref.referenced next unless ref && ref.kind != :cursor_invalid_file begin simple_name = ref.spelling next if simple_name.nil? || simple_name.empty? if substitutions.key?(simple_name) range = if decl_ref.extent.text.include?('<') decl_ref.spelling_name_range(0) else decl_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece]) end replacement = source_range_replacement(source_text, source_text_offset, range, substitutions[simple_name]) if replacement range_replacements << replacement.merge(kind: :decl) next end end next if ref.kind == :cursor_non_type_template_parameter if ref.kind == :cursor_cxx_method next if source_text.match?(/::#{Regexp.escape(simple_name)}\s*\(/) end qualified_name = if ref.kind == :cursor_enum_constant_decl && ref.semantic_parent.kind == :cursor_enum_decl && !ref.semantic_parent.enum_scoped? enum_parent = ref.semantic_parent.semantic_parent if enum_parent && enum_parent.kind == :cursor_namespace "#{enum_parent.qualified_name}::#{simple_name}" elsif ref.semantic_parent.anonymous? && enum_parent && enum_parent.kind != :cursor_translation_unit "#{enum_parent.qualified_name}::#{simple_name}" else ref.qualified_name end elsif ref.semantic_parent.kind == :cursor_class_template "#{ref.semantic_parent.qualified_display_name}::#{simple_name}" else ref.qualified_name end next if simple_name == qualified_name next if qualified_name.start_with?('::') next unless qualified_name.end_with?(simple_name) range = if decl_ref.extent.text.include?('<') decl_ref.spelling_name_range(0) else decl_ref.reference_name_range([:want_qualifier, :want_template_args, :want_single_piece]) end replacement = source_range_replacement(source_text, source_text_offset, range) if replacement start_index = replacement[:start_offset] - source_text_offset end_index = replacement[:end_offset] - source_text_offset span_text = source_text.byteslice(start_index, end_index - start_index) replacement_text = replacement_from_name_span(span_text, simple_name, qualified_name) if replacement_text && replacement_text != span_text range_replacements << replacement.merge(replacement: replacement_text, kind: :decl) next end end decl_fallbacks << [simple_name, qualified_name] rescue ArgumentError # Skip if we can't get qualified name end end decl_replacements = range_replacements.select { |replacement| replacement[:kind] == :decl } range_replacements.reject! do |replacement| replacement[:kind] == :type && decl_replacements.any? do |decl_replacement| decl_replacement[:start_offset] <= replacement[:start_offset] && decl_replacement[:end_offset] >= replacement[:end_offset] end end end range_replacements = collapse_same_start_replacements(range_replacements) source_text = apply_source_replacements(source_text, source_text_offset, range_replacements) unless range_replacements.empty? type_fallbacks.each do |type_fallback| ref, simple_name, qualified_name, is_dependent_typedef = type_fallback source_text = fallback_qualify_type_reference(source_text, ref, simple_name, qualified_name, is_dependent_typedef) end decl_fallbacks.each do |decl_fallback| simple_name, qualified_name = decl_fallback source_text = fallback_qualify_declaration_reference(source_text, simple_name, qualified_name) end source_text end |
#replacement_from_name_span(span_text, simple_name, qualified_name) ⇒ Object
Expand the written name span to the desired fully qualified form without dropping template args or clobbering existing qualifiers.
Examples:
span_text = 'helper'
simple_name = 'helper'
qualified_name = 'quoted::helper'
=> 'quoted::helper'
span_text = 'makePtr<inner::IndexParams>'
simple_name = 'makePtr'
qualified_name = 'outer::makePtr'
=> 'outer::makePtr<inner::IndexParams>'
span_text = 'PerfLevel::SLOW'
simple_name = 'SLOW'
qualified_name = 'multiline::PerfLevel::SLOW'
=> 'multiline::PerfLevel::SLOW'
205 206 207 208 209 210 211 |
# File 'lib/ruby-bindgen/generators/rice/reference_qualifier.rb', line 205 def replacement_from_name_span(span_text, simple_name, qualified_name) return qualified_name if span_text == simple_name return qualified_name if span_text.include?('::') && span_text.end_with?(simple_name) return span_text.sub(/\A#{Regexp.escape(simple_name)}/, qualified_name) if span_text.start_with?(simple_name) nil end |
#top_level_default_separator_offset(text) ⇒ Object
Find the byte offset of the declaration’s top-level default-value ‘=`.
Examples:
'FILE* stream = stdout'
=> offset of the `=` before stdout
'template<typename U = int> class Container = Box'
=> offset of the `=` before Box, not the inner `= int`
Nested delimiters are skipped so ‘=` inside template parameter lists, function types, arrays, and braced expressions does not get mistaken for the declaration’s own default separator.
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/ruby-bindgen/generators/rice/reference_qualifier.rb', line 225 def top_level_default_separator_offset(text) return nil if text.nil? || text.empty? angle_depth = 0 paren_depth = 0 bracket_depth = 0 brace_depth = 0 in_single_quote = false in_double_quote = false escaped = false byte_offset = 0 text.each_char do |char| if in_single_quote || in_double_quote if escaped escaped = false elsif char == '\\' escaped = true elsif in_single_quote && char == "'" in_single_quote = false elsif in_double_quote && char == '"' in_double_quote = false end else case char when "'" in_single_quote = true when '"' in_double_quote = true when '<' angle_depth += 1 when '>' angle_depth -= 1 if angle_depth > 0 when '(' paren_depth += 1 when ')' paren_depth -= 1 if paren_depth > 0 when '[' bracket_depth += 1 when ']' bracket_depth -= 1 if bracket_depth > 0 when '{' brace_depth += 1 when '}' brace_depth -= 1 if brace_depth > 0 when '=' if angle_depth.zero? && paren_depth.zero? && bracket_depth.zero? && brace_depth.zero? return byte_offset end end end byte_offset += char.bytesize end nil end |