Class | RDoc::C_Parser |
In: |
parsers/parse_c.rb
|
Parent: | Object |
See rdoc/c_parse.rb
prepare to parse a C file
# File parsers/parse_c.rb, line 176 176: def initialize(top_level, file_name, body, options, stats) 177: @known_classes = KNOWN_CLASSES.dup 178: @body = handle_tab_width(handle_ifdefs_in(body)) 179: @options = options 180: @stats = stats 181: @top_level = top_level 182: @classes = Hash.new 183: @file_dir = File.dirname(file_name) 184: @progress = $stderr unless options.quiet 185: end
# File parsers/parse_c.rb, line 419 419: def do_aliases 420: @body.scan(%r{rb_define_alias\s*\(\s*(\w+),\s*"([^"]+)",\s*"([^"]+)"\s*\)}m) do 421: |var_name, new_name, old_name| 422: @stats.num_methods += 1 423: class_name = @known_classes[var_name] || var_name 424: class_obj = find_class(var_name, class_name) 425: 426: class_obj.add_alias(Alias.new("", old_name, new_name, "")) 427: end 428: end
# File parsers/parse_c.rb, line 282 282: def do_classes 283: @body.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do 284: |var_name, class_name| 285: handle_class_module(var_name, "module", class_name, nil, nil) 286: end 287: 288: # The '.' lets us handle SWIG-generated files 289: @body.scan(/([\w\.]+)\s* = \s*rb_define_class\s* 290: \( 291: \s*"(\w+)", 292: \s*(\w+)\s* 293: \)/mx) do 294: 295: |var_name, class_name, parent| 296: handle_class_module(var_name, "class", class_name, parent, nil) 297: end 298: 299: @body.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do 300: |var_name, class_name, parent| 301: parent = nil if parent == "0" 302: handle_class_module(var_name, "class", class_name, parent, nil) 303: end 304: 305: @body.scan(/(\w+)\s* = \s*rb_define_module_under\s* 306: \( 307: \s*(\w+), 308: \s*"(\w+)" 309: \s*\)/mx) do 310: 311: |var_name, in_module, class_name| 312: handle_class_module(var_name, "module", class_name, nil, in_module) 313: end 314: 315: @body.scan(/([\w\.]+)\s* = \s*rb_define_class_under\s* 316: \( 317: \s*(\w+), 318: \s*"(\w+)", 319: \s*(\w+)\s* 320: \s*\)/mx) do 321: 322: |var_name, in_module, class_name, parent| 323: handle_class_module(var_name, "class", class_name, parent, in_module) 324: end 325: 326: end
# File parsers/parse_c.rb, line 330 330: def do_constants 331: @body.scan(%r{\Wrb_define_ 332: ( 333: variable | 334: readonly_variable | 335: const | 336: global_const | 337: ) 338: \s*\( 339: (?:\s*(\w+),)? 340: \s*"(\w+)", 341: \s*(.*?)\s*\)\s*; 342: }xm) do 343: 344: |type, var_name, const_name, definition| 345: var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" 346: handle_constants(type, var_name, const_name, definition) 347: end 348: end
Look for includes of the form
rb_include_module(rb_cArray, rb_mEnumerable);
# File parsers/parse_c.rb, line 648 648: def do_includes 649: @body.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| 650: if cls = @classes[c] 651: m = @known_classes[m] || m 652: cls.add_include(Include.new(m, "")) 653: end 654: end 655: end
# File parsers/parse_c.rb, line 352 352: def do_methods 353: 354: @body.scan(%r{rb_define_ 355: ( 356: singleton_method | 357: method | 358: module_function | 359: private_method 360: ) 361: \s*\(\s*([\w\.]+), 362: \s*"([^"]+)", 363: \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, 364: \s*(-?\w+)\s*\) 365: (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? 366: }xm) do 367: |type, var_name, meth_name, meth_body, param_count, source_file| 368: #" 369: 370: # Ignore top-object and weird struct.c dynamic stuff 371: next if var_name == "ruby_top_self" 372: next if var_name == "nstr" 373: next if var_name == "envtbl" 374: next if var_name == "argf" # it'd be nice to handle this one 375: 376: var_name = "rb_cObject" if var_name == "rb_mKernel" 377: handle_method(type, var_name, meth_name, 378: meth_body, param_count, source_file) 379: end 380: 381: @body.scan(%r{rb_define_attr\( 382: \s*([\w\.]+), 383: \s*"([^"]+)", 384: \s*(\d+), 385: \s*(\d+)\s*\); 386: }xm) do #" 387: |var_name, attr_name, attr_reader, attr_writer| 388: 389: #var_name = "rb_cObject" if var_name == "rb_mKernel" 390: handle_attr(var_name, attr_name, 391: attr_reader.to_i != 0, 392: attr_writer.to_i != 0) 393: end 394: 395: @body.scan(%r{rb_define_global_function\s*\( 396: \s*"([^"]+)", 397: \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, 398: \s*(-?\w+)\s*\) 399: (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? 400: }xm) do #" 401: |meth_name, meth_body, param_count, source_file| 402: handle_method("method", "rb_mKernel", meth_name, 403: meth_body, param_count, source_file) 404: end 405: 406: @body.scan(/define_filetest_function\s*\( 407: \s*"([^"]+)", 408: \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, 409: \s*(-?\w+)\s*\)/xm) do #" 410: |meth_name, meth_body, param_count| 411: 412: handle_method("method", "rb_mFileTest", meth_name, meth_body, param_count) 413: handle_method("singleton_method", "rb_cFile", meth_name, meth_body, param_count) 414: end 415: end
# File parsers/parse_c.rb, line 496 496: def find_attr_comment(attr_name) 497: if @body =~ %r{((?>/\*.*?\*/\s+)) 498: rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi 499: $1 500: elsif @body =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m 501: $1 502: else 503: '' 504: end 505: end
Find the C code corresponding to a Ruby method
# File parsers/parse_c.rb, line 556 556: def find_body(meth_name, meth_obj, body, quiet = false) 557: case body 558: when %r{((?>/\*.*?\*/\s*))(?:static\s+)?VALUE\s+#{meth_name} 559: \s*(\([^)]*\))\s*\{.*?^\}}xm 560: comment, params = $1, $2 561: body_text = $& 562: 563: remove_private_comments(comment) if comment 564: 565: # see if we can find the whole body 566: 567: re = Regexp.escape(body_text) + '[^(]*^\{.*?^\}' 568: if Regexp.new(re, Regexp::MULTILINE).match(body) 569: body_text = $& 570: end 571: 572: # The comment block may have been overridden with a 573: # 'Document-method' block. This happens in the interpreter 574: # when multiple methods are vectored through to the same 575: # C method but those methods are logically distinct (for 576: # example Kernel.hash and Kernel.object_id share the same 577: # implementation 578: 579: override_comment = find_override_comment(meth_obj.name) 580: comment = override_comment if override_comment 581: 582: find_modifiers(comment, meth_obj) if comment 583: 584: # meth_obj.params = params 585: meth_obj.start_collecting_tokens 586: meth_obj.add_token(RubyToken::Token.new(1,1).set_text(body_text)) 587: meth_obj.comment = mangle_comment(comment) 588: when %r{((?>/\*.*?\*/\s*))^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m 589: comment = $1 590: find_body($2, meth_obj, body, true) 591: find_modifiers(comment, meth_obj) 592: meth_obj.comment = mangle_comment(comment) + meth_obj.comment 593: when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m 594: unless find_body($1, meth_obj, body, true) 595: warn "No definition for #{meth_name}" unless quiet 596: return false 597: end 598: else 599: 600: # No body, but might still have an override comment 601: comment = find_override_comment(meth_obj.name) 602: 603: if comment 604: find_modifiers(comment, meth_obj) 605: meth_obj.comment = mangle_comment(comment) 606: else 607: warn "No definition for #{meth_name}" unless quiet 608: return false 609: end 610: end 611: true 612: end
# File parsers/parse_c.rb, line 668 668: def find_class(raw_name, name) 669: unless @classes[raw_name] 670: if raw_name =~ /^rb_m/ 671: @classes[raw_name] = @top_level.add_module(NormalModule, name) 672: else 673: @classes[raw_name] = @top_level.add_class(NormalClass, name, nil) 674: end 675: end 676: @classes[raw_name] 677: end
# File parsers/parse_c.rb, line 269 269: def find_class_comment(class_name, class_meth) 270: comment = nil 271: if @body =~ %r{((?>/\*.*?\*/\s+)) 272: (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)\)}xmi 273: comment = $1 274: elsif @body =~ %r{Document-(class|module):\s#{class_name}\s*?\n((?>.*?\*/))}m 275: comment = $2 276: end 277: class_meth.comment = mangle_comment(comment) if comment 278: end
# File parsers/parse_c.rb, line 453 453: def find_const_comment(type, const_name) 454: if @body =~ %r{((?>/\*.*?\*/\s+)) 455: rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi 456: $1 457: elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m 458: $1 459: else 460: '' 461: end 462: end
If the comment block contains a section that looks like
call-seq: Array.new Array.new(10)
use it for the parameters
# File parsers/parse_c.rb, line 622 622: def find_modifiers(comment, meth_obj) 623: if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or 624: comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '') 625: meth_obj.document_self = false 626: end 627: if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or 628: comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') 629: seq = $1 630: seq.gsub!(/^\s*\*\s*/, '') 631: meth_obj.call_seq = seq 632: end 633: end
# File parsers/parse_c.rb, line 637 637: def find_override_comment(meth_name) 638: name = Regexp.escape(meth_name) 639: if @body =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m 640: $1 641: end 642: end
# File parsers/parse_c.rb, line 466 466: def handle_attr(var_name, attr_name, reader, writer) 467: rw = '' 468: if reader 469: #@stats.num_methods += 1 470: rw << 'R' 471: end 472: if writer 473: #@stats.num_methods += 1 474: rw << 'W' 475: end 476: 477: class_name = @known_classes[var_name] 478: 479: return unless class_name 480: 481: class_obj = find_class(var_name, class_name) 482: 483: if class_obj 484: comment = find_attr_comment(attr_name) 485: unless comment.empty? 486: comment = mangle_comment(comment) 487: end 488: att = Attr.new('', attr_name, rw, comment) 489: class_obj.add_attribute(att) 490: end 491: 492: end
# File parsers/parse_c.rb, line 228 228: def handle_class_module(var_name, class_mod, class_name, parent, in_module) 229: progress(class_mod[0, 1]) 230: 231: parent_name = @known_classes[parent] || parent 232: 233: if in_module 234: enclosure = @classes[in_module] || @@enclosure_classes[in_module] 235: unless enclosure 236: if enclosure = @known_classes[in_module] 237: handle_class_module(in_module, (/^rb_m/ =~ in_module ? "module" : "class"), 238: enclosure, nil, nil) 239: enclosure = @classes[in_module] 240: end 241: end 242: unless enclosure 243: warn("Enclosing class/module '#{in_module}' for " + 244: "#{class_mod} #{class_name} not known") 245: return 246: end 247: else 248: enclosure = @top_level 249: end 250: 251: if class_mod == "class" 252: cm = enclosure.add_class(NormalClass, class_name, parent_name) 253: @stats.num_classes += 1 254: else 255: cm = enclosure.add_module(NormalModule, class_name) 256: @stats.num_modules += 1 257: end 258: cm.record_location(enclosure.toplevel) 259: 260: find_class_comment(cm.full_name, cm) 261: @classes[var_name] = cm 262: @@enclosure_classes[var_name] = cm 263: @known_classes[var_name] = cm.full_name 264: end
# File parsers/parse_c.rb, line 432 432: def handle_constants(type, var_name, const_name, definition) 433: #@stats.num_constants += 1 434: class_name = @known_classes[var_name] 435: 436: return unless class_name 437: 438: class_obj = find_class(var_name, class_name) 439: 440: unless class_obj 441: warn("Enclosing class/module '#{const_name}' for not known") 442: return 443: end 444: 445: comment = find_const_comment(type, const_name) 446: 447: con = Constant.new(const_name, definition, mangle_comment(comment)) 448: class_obj.add_constant(con) 449: end
Remove ifdefs that would otherwise confuse us
# File parsers/parse_c.rb, line 693 693: def handle_ifdefs_in(body) 694: body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 } 695: end
# File parsers/parse_c.rb, line 509 509: def handle_method(type, var_name, meth_name, 510: meth_body, param_count, source_file = nil) 511: progress(".") 512: 513: @stats.num_methods += 1 514: class_name = @known_classes[var_name] 515: 516: return unless class_name 517: 518: class_obj = find_class(var_name, class_name) 519: 520: if class_obj 521: if meth_name == "initialize" 522: meth_name = "new" 523: type = "singleton_method" 524: end 525: meth_obj = AnyMethod.new("", meth_name) 526: meth_obj.singleton = 527: %w{singleton_method module_function}.include?(type) 528: 529: p_count = (Integer(param_count) rescue -1) 530: 531: if p_count < 0 532: meth_obj.params = "(...)" 533: elsif p_count == 0 534: meth_obj.params = "()" 535: else 536: meth_obj.params = "(" + 537: (1..p_count).map{|i| "p#{i}"}.join(", ") + 538: ")" 539: end 540: 541: if source_file 542: file_name = File.join(@file_dir, source_file) 543: body = (@@known_bodies[source_file] ||= File.read(file_name)) 544: else 545: body = @body 546: end 547: if find_body(meth_body, meth_obj, body) and meth_obj.document_self 548: class_obj.add_method(meth_obj) 549: end 550: end 551: end
# File parsers/parse_c.rb, line 679 679: def handle_tab_width(body) 680: if /\t/ =~ body 681: tab_width = Options.instance.tab_width 682: body.split(/\n/).map do |line| 683: 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #` 684: line 685: end .join("\n") 686: else 687: body 688: end 689: end
Remove the /*’s and leading asterisks from C comments
# File parsers/parse_c.rb, line 661 661: def mangle_comment(comment) 662: comment.sub!(%r{/\*+}) { " " * $&.length } 663: comment.sub!(%r{\*+/}) { " " * $&.length } 664: comment.gsub!(/^[ \t]*\*/m) { " " * $&.length } 665: comment 666: end
# File parsers/parse_c.rb, line 203 203: def progress(char) 204: unless @options.quiet 205: @progress.print(char) 206: @progress.flush 207: end 208: end
remove lines that are commented out that might otherwise get picked up when scanning for classes and methods
# File parsers/parse_c.rb, line 224 224: def remove_commented_out_lines 225: @body.gsub!(%r{//.*rb_define_}, '//') 226: end
# File parsers/parse_c.rb, line 216 216: def remove_private_comments(comment) 217: comment.gsub!(/\/?\*--(.*?)\/?\*\+\+/m, '') 218: comment.sub!(/\/?\*--.*/m, '') 219: end