| 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