| 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 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
# File parsers/parse_c.rb, line 412
412: def do_aliases
413: @body.scan(%r{rb_define_alias\s*\(\s*(\w+),\s*"([^"]+)",\s*"([^"]+)"\s*\)}m) do
414: |var_name, new_name, old_name|
415: @stats.num_methods += 1
416: class_name = @known_classes[var_name] || var_name
417: class_obj = find_class(var_name, class_name)
418:
419: class_obj.add_alias(Alias.new("", old_name, new_name, ""))
420: end
421: end
# File parsers/parse_c.rb, line 275
275: def do_classes
276: @body.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do
277: |var_name, class_name|
278: handle_class_module(var_name, "module", class_name, nil, nil)
279: end
280:
281: # The '.' lets us handle SWIG-generated files
282: @body.scan(/([\w\.]+)\s* = \s*rb_define_class\s*
283: \(
284: \s*"(\w+)",
285: \s*(\w+)\s*
286: \)/mx) do
287:
288: |var_name, class_name, parent|
289: handle_class_module(var_name, "class", class_name, parent, nil)
290: end
291:
292: @body.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do
293: |var_name, class_name, parent|
294: parent = nil if parent == "0"
295: handle_class_module(var_name, "class", class_name, parent, nil)
296: end
297:
298: @body.scan(/(\w+)\s* = \s*rb_define_module_under\s*
299: \(
300: \s*(\w+),
301: \s*"(\w+)"
302: \s*\)/mx) do
303:
304: |var_name, in_module, class_name|
305: handle_class_module(var_name, "module", class_name, nil, in_module)
306: end
307:
308: @body.scan(/([\w\.]+)\s* = \s*rb_define_class_under\s*
309: \(
310: \s*(\w+),
311: \s*"(\w+)",
312: \s*(\w+)\s*
313: \s*\)/mx) do
314:
315: |var_name, in_module, class_name, parent|
316: handle_class_module(var_name, "class", class_name, parent, in_module)
317: end
318:
319: end
# File parsers/parse_c.rb, line 323
323: def do_constants
324: @body.scan(%r{\Wrb_define_
325: (
326: variable |
327: readonly_variable |
328: const |
329: global_const |
330: )
331: \s*\(
332: (?:\s*(\w+),)?
333: \s*"(\w+)",
334: \s*(.*?)\s*\)\s*;
335: }xm) do
336:
337: |type, var_name, const_name, definition|
338: var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel"
339: handle_constants(type, var_name, const_name, definition)
340: end
341: end
Look for includes of the form
rb_include_module(rb_cArray, rb_mEnumerable);
# File parsers/parse_c.rb, line 638
638: def do_includes
639: @body.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m|
640: if cls = @classes[c]
641: m = @known_classes[m] || m
642: cls.add_include(Include.new(m, ""))
643: end
644: end
645: end
# File parsers/parse_c.rb, line 345
345: def do_methods
346:
347: @body.scan(%r{rb_define_
348: (
349: singleton_method |
350: method |
351: module_function |
352: private_method
353: )
354: \s*\(\s*([\w\.]+),
355: \s*"([^"]+)",
356: \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
357: \s*(-?\w+)\s*\)
358: (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))?
359: }xm) do
360: |type, var_name, meth_name, meth_body, param_count, source_file|
361: #"
362:
363: # Ignore top-object and weird struct.c dynamic stuff
364: next if var_name == "ruby_top_self"
365: next if var_name == "nstr"
366: next if var_name == "envtbl"
367: next if var_name == "argf" # it'd be nice to handle this one
368:
369: var_name = "rb_cObject" if var_name == "rb_mKernel"
370: handle_method(type, var_name, meth_name,
371: meth_body, param_count, source_file)
372: end
373:
374: @body.scan(%r{rb_define_attr\(
375: \s*([\w\.]+),
376: \s*"([^"]+)",
377: \s*(\d+),
378: \s*(\d+)\s*\);
379: }xm) do #"
380: |var_name, attr_name, attr_reader, attr_writer|
381:
382: #var_name = "rb_cObject" if var_name == "rb_mKernel"
383: handle_attr(var_name, attr_name,
384: attr_reader.to_i != 0,
385: attr_writer.to_i != 0)
386: end
387:
388: @body.scan(%r{rb_define_global_function\s*\(
389: \s*"([^"]+)",
390: \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
391: \s*(-?\w+)\s*\)
392: (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))?
393: }xm) do #"
394: |meth_name, meth_body, param_count, source_file|
395: handle_method("method", "rb_mKernel", meth_name,
396: meth_body, param_count, source_file)
397: end
398:
399: @body.scan(/define_filetest_function\s*\(
400: \s*"([^"]+)",
401: \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
402: \s*(-?\w+)\s*\)/xm) do #"
403: |meth_name, meth_body, param_count|
404:
405: handle_method("method", "rb_mFileTest", meth_name, meth_body, param_count)
406: handle_method("singleton_method", "rb_cFile", meth_name, meth_body, param_count)
407: end
408: end
# File parsers/parse_c.rb, line 489
489: def find_attr_comment(attr_name)
490: if @body =~ %r{((?>/\*.*?\*/\s+))
491: rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi
492: $1
493: elsif @body =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m
494: $1
495: else
496: ''
497: end
498: end
Find the C code corresponding to a Ruby method
# File parsers/parse_c.rb, line 548
548: def find_body(meth_name, meth_obj, body, quiet = false)
549: case body
550: when %r{((?>/\*.*?\*/\s*))(?:static\s+)?VALUE\s+#{meth_name}
551: \s*(\(.*?\)).*?^}xm
552: comment, params = $1, $2
553: body_text = $&
554:
555: # see if we can find the whole body
556:
557: re = Regexp.escape(body_text) + '[^(]*^\{.*?^\}'
558: if Regexp.new(re, Regexp::MULTILINE).match(body)
559: body_text = $&
560: end
561:
562: # The comment block may have been overridden with a
563: # 'Document-method' block. This happens in the interpreter
564: # when multiple methods are vectored through to the same
565: # C method but those methods are logically distinct (for
566: # example Kernel.hash and Kernel.object_id share the same
567: # implementation
568:
569: override_comment = find_override_comment(meth_obj.name)
570: comment = override_comment if override_comment
571:
572: find_modifiers(comment, meth_obj) if comment
573:
574: # meth_obj.params = params
575: meth_obj.start_collecting_tokens
576: meth_obj.add_token(RubyToken::Token.new(1,1).set_text(body_text))
577: meth_obj.comment = mangle_comment(comment)
578: when %r{((?>/\*.*?\*/\s*))^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
579: comment = $1
580: find_body($2, meth_obj, body, true)
581: find_modifiers(comment, meth_obj)
582: meth_obj.comment = mangle_comment(comment) + meth_obj.comment
583: when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
584: unless find_body($1, meth_obj, body, true)
585: warn "No definition for #{meth_name}" unless quiet
586: return false
587: end
588: else
589:
590: # No body, but might still have an override comment
591: comment = find_override_comment(meth_obj.name)
592:
593: if comment
594: find_modifiers(comment, meth_obj)
595: meth_obj.comment = mangle_comment(comment)
596: else
597: warn "No definition for #{meth_name}" unless quiet
598: return false
599: end
600: end
601: true
602: end
# File parsers/parse_c.rb, line 658
658: def find_class(raw_name, name)
659: unless @classes[raw_name]
660: if raw_name =~ /^rb_m/
661: @classes[raw_name] = @top_level.add_module(NormalModule, name)
662: else
663: @classes[raw_name] = @top_level.add_class(NormalClass, name, nil)
664: end
665: end
666: @classes[raw_name]
667: end
# File parsers/parse_c.rb, line 262
262: def find_class_comment(class_name, class_meth)
263: comment = nil
264: if @body =~ %r{((?>/\*.*?\*/\s+))
265: (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)\)}xmi
266: comment = $1
267: elsif @body =~ %r{Document-(class|module):\s#{class_name}\s*?\n((?>.*?\*/))}m
268: comment = $2
269: end
270: class_meth.comment = mangle_comment(comment) if comment
271: end
# File parsers/parse_c.rb, line 446
446: def find_const_comment(type, const_name)
447: if @body =~ %r{((?>/\*.*?\*/\s+))
448: rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi
449: $1
450: elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m
451: $1
452: else
453: ''
454: end
455: 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 612
612: def find_modifiers(comment, meth_obj)
613: if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or
614: comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '')
615: meth_obj.document_self = false
616: end
617: if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or
618: comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '')
619: seq = $1
620: seq.gsub!(/^\s*\*\s*/, '')
621: meth_obj.call_seq = seq
622: end
623: end
# File parsers/parse_c.rb, line 627
627: def find_override_comment(meth_name)
628: name = Regexp.escape(meth_name)
629: if @body =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m
630: $1
631: end
632: end
# File parsers/parse_c.rb, line 459
459: def handle_attr(var_name, attr_name, reader, writer)
460: rw = ''
461: if reader
462: #@stats.num_methods += 1
463: rw << 'R'
464: end
465: if writer
466: #@stats.num_methods += 1
467: rw << 'W'
468: end
469:
470: class_name = @known_classes[var_name]
471:
472: return unless class_name
473:
474: class_obj = find_class(var_name, class_name)
475:
476: if class_obj
477: comment = find_attr_comment(attr_name)
478: unless comment.empty?
479: comment = mangle_comment(comment)
480: end
481: att = Attr.new('', attr_name, rw, comment)
482: class_obj.add_attribute(att)
483: end
484:
485: end
# File parsers/parse_c.rb, line 222
222: def handle_class_module(var_name, class_mod, class_name, parent, in_module)
223: progress(class_mod[0, 1])
224:
225: parent_name = @known_classes[parent] || parent
226:
227: if in_module
228: enclosure = @classes[in_module]
229: unless enclosure
230: if enclosure = @known_classes[in_module]
231: handle_class_module(in_module, (/^rb_m/ =~ in_module ? "module" : "class"),
232: enclosure, nil, nil)
233: enclosure = @classes[in_module]
234: end
235: end
236: unless enclosure
237: warn("Enclosing class/module '#{in_module}' for " +
238: "#{class_mod} #{class_name} not known")
239: return
240: end
241: else
242: enclosure = @top_level
243: end
244:
245: if class_mod == "class"
246: cm = enclosure.add_class(NormalClass, class_name, parent_name)
247: @stats.num_classes += 1
248: else
249: cm = enclosure.add_module(NormalModule, class_name)
250: @stats.num_modules += 1
251: end
252: cm.record_location(enclosure.toplevel)
253:
254: find_class_comment(cm.full_name, cm)
255: @classes[var_name] = cm
256: @known_classes[var_name] = cm.full_name
257: end
# File parsers/parse_c.rb, line 425
425: def handle_constants(type, var_name, const_name, definition)
426: #@stats.num_constants += 1
427: class_name = @known_classes[var_name]
428:
429: return unless class_name
430:
431: class_obj = find_class(var_name, class_name)
432:
433: unless class_obj
434: warn("Enclosing class/module '#{const_name}' for not known")
435: return
436: end
437:
438: comment = find_const_comment(type, const_name)
439:
440: con = Constant.new(const_name, definition, mangle_comment(comment))
441: class_obj.add_constant(con)
442: end
Remove ifdefs that would otherwise confuse us
# File parsers/parse_c.rb, line 683
683: def handle_ifdefs_in(body)
684: body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 }
685: end
# File parsers/parse_c.rb, line 502
502: def handle_method(type, var_name, meth_name,
503: meth_body, param_count, source_file = nil)
504: progress(".")
505:
506: @stats.num_methods += 1
507: class_name = @known_classes[var_name]
508:
509: return unless class_name
510:
511: class_obj = find_class(var_name, class_name)
512:
513: if class_obj
514: if meth_name == "initialize"
515: meth_name = "new"
516: type = "singleton_method"
517: end
518: meth_obj = AnyMethod.new("", meth_name)
519: meth_obj.singleton = type == "singleton_method"
520:
521: p_count = (Integer(param_count) rescue -1)
522:
523: if p_count < 0
524: meth_obj.params = "(...)"
525: elsif p_count == 0
526: meth_obj.params = "()"
527: else
528: meth_obj.params = "(" +
529: (1..p_count).map{|i| "p#{i}"}.join(", ") +
530: ")"
531: end
532:
533: if source_file
534: file_name = File.join(@file_dir, source_file)
535: body = (@@known_bodies[source_file] ||= File.read(file_name))
536: else
537: body = @body
538: end
539: if find_body(meth_body, meth_obj, body) and meth_obj.document_self
540: class_obj.add_method(meth_obj)
541: end
542: end
543: end
# File parsers/parse_c.rb, line 669
669: def handle_tab_width(body)
670: if /\t/ =~ body
671: tab_width = Options.instance.tab_width
672: body.split(/\n/).map do |line|
673: 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #`
674: line
675: end .join("\n")
676: else
677: body
678: end
679: end
Remove the /*’s and leading asterisks from C comments
# File parsers/parse_c.rb, line 651
651: def mangle_comment(comment)
652: comment.sub!(%r{/\*+}) { " " * $&.length }
653: comment.sub!(%r{\*+/}) { " " * $&.length }
654: comment.gsub!(/^[ \t]*\*/m) { " " * $&.length }
655: comment
656: end
# File 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 parsers/parse_c.rb, line 218
218: def remove_commented_out_lines
219: @body.gsub!(%r{//.*rb_define_}, '//')
220: end