Class: Rbind::ClangParser
- Inherits:
-
RNamespace
- Object
- RBase
- RDataType
- RNamespace
- Rbind::ClangParser
- Extended by:
- Logger
- Includes:
- Hooks
- Defined in:
- lib/rbind/clang_parser.rb
Defined Under Namespace
Classes: ClangParserError
Instance Attribute Summary
Attributes included from Logger
Attributes inherited from RNamespace
#consts, #operation_alias, #operations, #root, #type_alias, #used_namespaces
Attributes inherited from RDataType
#cdelete_method, #check_type, #invalid_value, #typedef
Attributes inherited from RBase
#alias, #auto_alias, #cname, #csignature, #doc, #extern_package_name, #ignore, #name, #namespace, #owner, #signature, #version
Class Method Summary collapse
Instance Method Summary collapse
-
#initialize(root = nil) ⇒ ClangParser
constructor
A new instance of ClangParser.
- #normalize_accessor(accessor) ⇒ Object
- #parse(file_path, args = ClangParser.default_arguments) ⇒ Object
-
#pointee_type(clang_type) ⇒ Object
returns the real type and the pointer level char** would be returned as [char,2].
-
#process(cursor, parent = self, access = :public) ⇒ Object
entry call to parse a file.
- #process_class(cursor, parent, access) ⇒ Object
- #process_class_template(cursor, parent, access) ⇒ Object
- #process_enum(cursor, parent) ⇒ Object
- #process_field(cursor, parent) ⇒ Object
- #process_function(cursor, parent) ⇒ Object
- #process_namespace(cursor, parent) ⇒ Object
-
#process_parameter(cursor, parent, type_getter = :type) ⇒ Object
type_getter is also used for :result_type.
- #process_typedef(cu, parent) ⇒ Object
- #process_variable(cursor, parent) ⇒ Object
-
#to_rbind_type(parent, cursor, rbind_type = nil, type_getter = :type, canonical = true, use_fallback = true) ⇒ Object
if rbind_type is given only pointer/ref or qualifier are applied.
Methods included from Logger
Methods inherited from RNamespace
#add_const, #add_default_types, #add_namespace, #add_operation, #add_simple_type, #add_simple_types, #add_type, #add_type_alias, #const, #constructor?, #container?, default_operators, #delete_type, #each_const, #each_container, #each_operation, #each_type, #empty?, #extern?, #method_missing, #operation, #operation?, #pretty_print, #pretty_print_name, #root?, #type, #types, #use_namespace
Methods inherited from RDataType
#==, #basic_type?, #check_type?, #cname, #const?, #container?, #ownership?, #ptr?, #raw?, #ref?, #remove_const, #remove_ownership, #remove_ptr, #remove_ref, #template?, #to_const, #to_ownership, #to_ptr, #to_raw, #to_ref, #to_single_ptr, #typedef?
Methods inherited from RBase
basename, #binding, #delete!, #doc?, #extern?, #full_name, #generate_signatures, #ignore?, #map_to_namespace, namespace, #namespace?, normalize, #pretty_print, #rename, #specialize_ruby, split_name, to_cname, #to_s
Constructor Details
#initialize(root = nil) ⇒ ClangParser
Returns a new instance of ClangParser.
58 59 60 61 62 |
# File 'lib/rbind/clang_parser.rb', line 58 def initialize(root=nil) super(nil,root) add_default_types if !root @clang = Clang::Clang.new end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class Rbind::RNamespace
Class Method Details
.default_arguments ⇒ Object
43 44 45 |
# File 'lib/rbind/clang_parser.rb', line 43 def self.default_arguments @default_arguments || ["-xc++","-fno-rtti"] end |
.default_arguments=(args) ⇒ Object
47 48 49 50 51 52 |
# File 'lib/rbind/clang_parser.rb', line 47 def self.default_arguments=(args) if not args.kind_of?(Array) raise ArgumentError, "Clang::default_arguments require Array" end @default_arguments = args end |
.reset_default_arguments ⇒ Object
54 55 56 |
# File 'lib/rbind/clang_parser.rb', line 54 def self.reset_default_arguments @default_arguments = [] end |
Instance Method Details
#normalize_accessor(accessor) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/rbind/clang_parser.rb', line 70 def normalize_accessor(accessor) case accessor when :x_public :public when :x_private :private when :x_protected :protected else raise "Cannot normalize accessor #{accessor}" end end |
#parse(file_path, args = ClangParser.default_arguments) ⇒ Object
64 65 66 67 68 |
# File 'lib/rbind/clang_parser.rb', line 64 def parse(file_path, args = ClangParser.default_arguments) tu = @clang.translation_unit(file_path,args) process(tu.cursor) self end |
#pointee_type(clang_type) ⇒ Object
returns the real type and the pointer level char** would be returned as [char,2]
85 86 87 88 89 90 91 92 93 |
# File 'lib/rbind/clang_parser.rb', line 85 def pointee_type(clang_type) # count pointer level level = 0 while(clang_type.kind == :pointer) clang_type = clang_type.pointee_type level += 1 end [clang_type,level] end |
#process(cursor, parent = self, access = :public) ⇒ Object
entry call to parse a file
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/rbind/clang_parser.rb', line 157 def process(cursor, parent = self, access = :public) last_obj = nil cursor.visit_children(false) do |cu,cu_parent| ClangParser.log.debug "process ----->#{cu.kind} #{cu.spelling} #{cu.type.kind} #{cu.specialized_template.kind} ---> expr: #{cu.expression.join('|')} -- public: #{cu.public?} access: #{access}" begin last_obj = case cu.kind when :namespace process_namespace(cu,parent) when :enum_decl process_enum(cu,parent) if access == :public when :union_decl # puts "got union declaration #{cu.spelling}" when :struct_decl process_class(cu,parent,:public) if access == :public when :class_decl process_class(cu,parent, :private) if access == :public when :function_decl process_function(cu,parent) if access == :public when :macro_expansion # CV_WRAP ... # puts "got macro #{cu.spelling} #{cu.location}" when :function_template # puts "got template fuction #{cu.spelling} #{cu.location}" when :class_template process_class_template(cu,parent, access) if access == :public when :template_type_parameter if !cu.spelling.empty? parent.add_type(RTemplateParameter.new(cu.spelling)) else ClangParser.log.info "no template parameter name" end when :x_access_specifier access = normalize_accessor(cu.cxx_access_specifier) when :x_base_specifier if access == :public next end local_access = normalize_accessor(cu.cxx_access_specifier) klass_name = cu.spelling if cu.spelling =~ /\s?([^\s]+$)/ klass_name = $1 end ClangParser.log.info "auto add parent class #{klass_name} if needed" p = parent.type(RClass.new(RBase.normalize(klass_name)), true) parent.add_parent p,local_access when :field_decl process_field(cu,parent) if access == :public when :constructor if access == :public f = process_function(cu,parent) f.return_type = nil if f f end when :x_method process_function(cu,parent) if access == :public when :typedef_decl if access != :public next end # rename object if parent has no name if last_obj && last_obj.respond_to?(:name) && last_obj.name =~ /no_name/ ClangParser.log.info "rename #{last_obj.name} to #{cu.spelling}" last_obj.rename(cu.spelling) last_obj else process_typedef(cu, parent) end when :var_decl process_variable(cu,parent) if access == :public when :unexposed_decl process(cu) if access == :public else #puts "skip: #{cu.spelling}" end rescue => e ClangParser.log.debug "Parsing failed -- skipping" end raise ClangParserError.new("jjj",cu) if last_obj.is_a? Fixnum end end |
#process_class(cursor, parent, access) ⇒ Object
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/rbind/clang_parser.rb', line 314 def process_class(cursor,parent,access) class_name = cursor.spelling class_name = if class_name.empty? "no_name_class" else class_name end if cursor.incomplete? ClangParser.log.info "skipping incomplete class #{parent}::#{class_name}" return else ClangParser.log.info "processing class #{parent}::#{class_name}" end klass = parent.type(class_name,false) klass = if(!klass) klass = RClass.new(class_name) parent.add_type(klass) klass else if klass.empty? ClangParser.log.info " reopening existing class #{klass}" klass elsif klass.template? ClangParser.log.info " skipping template #{name}" nil else ClangParser.log.warn " skipping non empty class #{name}" #raise "Cannot reopening existing class #{klass} which is non-empty!" nil end end #klass.extern_package_name = nil process(cursor,klass,access) if klass klass end |
#process_class_template(cursor, parent, access) ⇒ Object
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/rbind/clang_parser.rb', line 297 def process_class_template(cursor,parent,access) class_name = cursor.spelling ClangParser.log.info "processing class template #{parent}::#{class_name}" klass = parent.type(class_name,false) klass = if(!klass) klass = RTemplateClass.new(class_name) parent.add_type(klass) klass else ClangParser.log.info " reopening existing class template #{klass}" klass end process(cursor,klass, access) klass end |
#process_enum(cursor, parent) ⇒ Object
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 |
# File 'lib/rbind/clang_parser.rb', line 247 def process_enum(cursor,parent) name = cursor.spelling name = if name.empty? n = 0.upto(10000) do |i| n = "no_name_enum_#{i}" break n if !parent.type(n,false,false) end raise "Cannot find unique enum name" unless n n else name end ClangParser.log.info "processing enum #{parent}::#{name}" enum = REnum.new(name) cursor.visit_children(false) do |cu,_| case cu.kind when :enum_constant_decl # for now there is no api to access these values from libclang expression = cu.expression expression.pop val = if expression.join(" ") =~ /=(.*)/ $1.gsub(" ","") end enum.add_value(cu.spelling,val) end end parent.add_type(enum) enum end |
#process_field(cursor, parent) ⇒ Object
287 288 289 290 291 292 293 294 295 |
# File 'lib/rbind/clang_parser.rb', line 287 def process_field(cursor,parent) name = cursor.spelling ClangParser.log.info "processing field #{parent}::#{name}" var = process_parameter(cursor,parent) # TODO check for read write access a = RAttribute.new(var.name,var.type).writeable! parent.add_attribute a a end |
#process_function(cursor, parent) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/rbind/clang_parser.rb', line 351 def process_function(cursor,parent) name = cursor.spelling args = [] cursor = if(cursor.specialized_template.kind == :function_template) cursor.specialized_template else cursor end cursor.visit_children() do |cu,_| case cu.kind when :parm_decl p = process_parameter(cu,parent) args << p end end # some default values are not parsed by clang # try to parse them from Tokens # and rename parameters with unknown name # to prevent name clashes expression = cursor.expression.join() args.each_with_index do |arg,idx| if(!arg.default_value && (expression =~ /#{arg.name}(=\w*)?[,)]/)) if $1 and !$1.empty? arg.default_value = $1.sub("=","") end end arg.name = if(arg.name == "no_name_arg") arg.name + idx.to_s else arg.name end end result_type = if !cursor.result_type.null? process_parameter(cursor,parent,:result_type).type end op = ::Rbind::ROperation.new(name,result_type,*args) op = if cursor.static? op.to_static else op end ClangParser.log.info "add function #{op.signature}" parent.add_operation(op) op rescue RuntimeError => e ClangParser.log.info "skipping instance method #{parent.full_name}::#{name}: #{e}" nil end |
#process_namespace(cursor, parent) ⇒ Object
239 240 241 242 243 244 245 |
# File 'lib/rbind/clang_parser.rb', line 239 def process_namespace(cursor,parent) name = cursor.spelling ClangParser.log.info "processing namespace #{parent}::#{name}" ns = parent.add_namespace(name) process(cursor,ns) ns end |
#process_parameter(cursor, parent, type_getter = :type) ⇒ Object
type_getter is also used for :result_type
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'lib/rbind/clang_parser.rb', line 404 def process_parameter(cursor,parent,type_getter = :type) ClangParser.log.debug "process_parameter: spelling: '#{cursor.spelling}' type: '#{cursor.type}' kind '#{cursor.type.kind} parent #{parent}" para_name = cursor.spelling para_name = if para_name.empty? "no_name_arg" else para_name end default_value = nil type_cursor = nil template_name = "" name_space = [] cursor.visit_children do |cu,_| ClangParser.log.info "process parameter: cursor kind: #{cu.kind} #{cu.expression}" case cu.kind when :integer_literal exp = cu.expression exp.pop default_value = exp.join("") when :floating_literal exp = cu.expression exp.pop default_value = exp.join("") when :call_expr exp = cu.expression exp.shift exp.pop default_value = exp.join("") when :gnu_null_expr default_value = 0 when :unexposed_expr exp = cu.expression exp.pop default_value = exp.join("") when :template_ref name_space << cu.spelling if !template_name.empty? template_name += "<#{name_space.join("::")}" else template_name = name_space.join("::") end name_space.clear when :namespace_ref name_space << cu.spelling when :type_ref type_cursor = cu when :compound_stmt when :parm_decl when :member_ref when :constant_array exp = cu.expression exp.pop default_value = exp.gsub("{","[") default_value = default_value.gsub("}","]") end end type = if template_name.empty? type = if type_cursor to_rbind_type(parent,type_cursor) end # just upgrade type to pointer / ref if type != nil to_rbind_type(parent,cursor,type,type_getter) else # parameter is a template type # TODO find better way to get inner type # we could use type_cursor here if given but this is # not the case for basic types and somehow the type # qualifier are not provided expression = cursor.expression.join(" ") inner_types = if expression =~ /<([ \w\*&,:]*)>/ $1 else raise RuntimeError,"Cannot parse template type parameter." end inner_types = inner_types.split(",").map do |inner_type| parent.type(inner_type) end templates = template_name.split("<") templates << inner_types.map(&:full_name).join(",") t = parent.type(templates.join("<")+">"*(templates.size-1),true) to_rbind_type(parent,cursor,t,type_getter) end RParameter.new(para_name,type,default_value) rescue RuntimeError => e raise ClangParserError.new(e.to_s,cursor) end |
#process_typedef(cu, parent) ⇒ Object
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/rbind/clang_parser.rb', line 495 def process_typedef(cu, parent) ClangParser.log.debug "process_typedef: #{cu}: expression: #{cu.expression} parent: #{parent}" exp = cu.expression.join(" ") # Remove typedef label and extract orig type and alias orig_and_alias = exp.sub("typedef","") orig_and_alias = orig_and_alias.sub(";","").strip orig_type = nil alias_type = nil if orig_and_alias =~ /(.*)\s+([^\s]+)/ orig_type = $1 alias_type = $2 else raise RuntimeError,"Cannot parse typedef expression #{exp}" end begin t = parent.type(orig_type) ClangParser.log.debug "process_typedef: orig: '#{orig_type}' alias: #{alias_type}" parent.add_type_alias(t, alias_type) rescue RuntimeError => e ClangParser.log.warn "Cannot process typedef expression for orig: '#{orig_type}' alias: '#{alias_type}' : #{e}" end parent end |
#process_variable(cursor, parent) ⇒ Object
277 278 279 280 281 282 283 284 285 |
# File 'lib/rbind/clang_parser.rb', line 277 def process_variable(cursor,parent) name = cursor.spelling ClangParser.log.info "processing variable #{parent}::#{name}" var = process_parameter(cursor,parent) if var.type.const? parent.add_const(var) end var end |
#to_rbind_type(parent, cursor, rbind_type = nil, type_getter = :type, canonical = true, use_fallback = true) ⇒ Object
if rbind_type is given only pointer/ref or qualifier are applied
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 |
# File 'lib/rbind/clang_parser.rb', line 96 def to_rbind_type(parent,cursor,rbind_type=nil,type_getter = :type,canonical = true, use_fallback = true) ClangParser.log.debug "Parent: #{parent} --> cursor: #{cursor.expression}, spelling #{cursor.spelling}" clang_type = cursor.send(type_getter) return nil if clang_type.null? clang_type = clang_type.canonical_type if canonical clang_type,level = pointee_type(clang_type) t = if rbind_type rbind_type else name = clang_type.declaration.spelling name = if name.empty? if clang_type.kind != :unexposed if clang_type.kind == :l_value_reference clang_type.pointee_type.kind else clang_type.kind.to_s end else # fall back to cursor spelling cursor.spelling end else namespace = clang_type.declaration.namespace if namespace.empty? name else "#{namespace}::#{name}" end end t = parent.type(name,!canonical) end # try again without canonical when type could not be found or type is template if use_fallback if !t || t.template? return to_rbind_type(parent,cursor,rbind_type,type_getter,false, false) end end # add pointer level 1.upto(level) do t = t.to_ptr end t = if clang_type.kind == :l_value_reference if clang_type.pointee_type.const_qualified? t.to_ref.to_const else t.to_ref end else if clang_type.const_qualified? t.to_const else t end end end |