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