diff options
Diffstat (limited to 'lib/rdoc/parser/c.rb')
-rw-r--r-- | lib/rdoc/parser/c.rb | 414 |
1 files changed, 301 insertions, 113 deletions
diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index e218298dfe..60e9fefd61 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -3,9 +3,9 @@ require 'rdoc/parser/ruby' require 'rdoc/known_classes' ## -# We attempt to parse C extension files. Basically we look for +# RDoc::Parser::C attempts to parse C extension files. It looks for # the standard patterns that you find in extensions: <tt>rb_define_class, -# rb_define_method</tt> and so on. We also try to find the corresponding +# rb_define_method</tt> and so on. It tries to find the corresponding # C source for the methods and extract comments, but if we fail # we don't worry too much. # @@ -49,13 +49,26 @@ require 'rdoc/known_classes' # # The comment blocks may include special directives: # -# [Document-class: <i>name</i>] -# This comment block is documentation for the given class. Use this -# when the <tt>Init_xxx</tt> method is not named after the class. +# [Document-class: +name+] +# Documentation for the named class. # -# [Document-method: <i>name</i>] -# This comment documents the named method. Use when RDoc cannot -# automatically find the method from it's declaration +# [Document-module: +name+] +# Documentation for the named module. +# +# [Document-const: +name+] +# Documentation for the named +rb_define_const+. +# +# [Document-global: +name+] +# Documentation for the named +rb_define_global_const+ +# +# [Document-variable: +name+] +# Documentation for the named +rb_define_variable+ +# +# [Document-method: +name+] +# Documentation for the named method. +# +# [Document-attr: +name+] +# Documentation for the named attribute. # # [call-seq: <i>text up to an empty line</i>] # Because C source doesn't give descripive names to Ruby-level parameters, @@ -120,21 +133,61 @@ class RDoc::Parser::C < RDoc::Parser @known_classes = RDoc::KNOWN_CLASSES.dup @content = handle_tab_width handle_ifdefs_in(@content) @classes = Hash.new + @singleton_classes = Hash.new @file_dir = File.dirname(@file_name) end + ## + # Scans #content for rb_define_alias + def do_aliases - @content.scan(%r{rb_define_alias\s*\(\s*(\w+),\s*"([^"]+)",\s*"([^"]+)"\s*\)}m) do - |var_name, new_name, old_name| + @content.scan(/rb_define_alias\s*\( + \s*(\w+), + \s*"(.+?)", + \s*"(.+?)" + \s*\)/xm) do |var_name, new_name, old_name| class_name = @known_classes[var_name] || var_name - class_obj = find_class(var_name, class_name) + class_obj = find_class var_name, class_name + + al = RDoc::Alias.new '', old_name, new_name, '' + al.singleton = @singleton_classes.key?(var_name) + + comment = find_alias_comment var_name, new_name, old_name + comment = strip_stars comment + al.comment = comment + + class_obj.add_alias al + @stats.add_alias al + end + end - as = class_obj.add_alias RDoc::Alias.new("", old_name, new_name, "") + ## + # Scans #content for rb_attr and rb_define_attr + + def do_attrs + @content.scan(/rb_attr\s*\( + \s*(\w+), + \s*([\w"()]+), + \s*([01]), + \s*([01]), + \s*\w+\);/xm) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end - @stats.add_alias as + @content.scan(%r%rb_define_attr\( + \s*([\w\.]+), + \s*"([^"]+)", + \s*(\d+), + \s*(\d+)\s*\); + %xm) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write end end + ## + # Scans #content for rb_define_module, rb_define_class, boot_defclass, + # rb_define_module_under, rb_define_class_under and rb_singleton_class + def do_classes @content.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do |var_name, class_name| @@ -165,35 +218,44 @@ class RDoc::Parser::C < RDoc::Parser end @content.scan(/([\w\.]+)\s* = \s*rb_define_class_under\s* - \( - \s*(\w+), - \s*"(\w+)", - \s*([\w\*\s\(\)\.\->]+)\s* # for SWIG - \s*\)/mx) do |var_name, in_module, class_name, parent| + \( + \s*(\w+), + \s*"(\w+)", + \s*([\w\*\s\(\)\.\->]+)\s* # for SWIG + \s*\)/mx) do |var_name, in_module, class_name, parent| handle_class_module(var_name, "class", class_name, parent, in_module) end + + @content.scan(/([\w\.]+)\s* = \s*rb_singleton_class\s* + \( + \s*(\w+) + \s*\)/mx) do |sclass_var, class_var| + handle_singleton sclass_var, class_var + end end + ## + # Scans #content for rb_define_variable, rb_define_readonly_variable, + # rb_define_const and rb_define_global_const + def do_constants - @content.scan(%r{\Wrb_define_ + @content.scan(%r%\Wrb_define_ ( variable | readonly_variable | const | - global_const | ) + global_const ) \s*\( (?:\s*(\w+),)? \s*"(\w+)", \s*(.*?)\s*\)\s*; - }xm) do |type, var_name, const_name, definition| + %xm) do |type, var_name, const_name, definition| var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" handle_constants type, var_name, const_name, definition end end ## - # Look for includes of the form: - # - # rb_include_module(rb_cArray, rb_mEnumerable); + # Scans #content for rb_include_module def do_includes @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| @@ -204,8 +266,13 @@ class RDoc::Parser::C < RDoc::Parser end end + ## + # Scans #content for rb_define_method, rb_define_singleton_method, + # rb_define_module_function, rb_define_private_method, + # rb_define_global_function and define_filetest_function + def do_methods - @content.scan(%r{rb_define_ + @content.scan(%r%rb_define_ ( singleton_method | method | @@ -217,8 +284,7 @@ class RDoc::Parser::C < RDoc::Parser \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, \s*(-?\w+)\s*\) (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - }xm) do - |type, var_name, meth_name, meth_body, param_count, source_file| + %xm) do |type, var_name, meth_name, meth_body, param_count, source_file| # Ignore top-object and weird struct.c dynamic stuff next if var_name == "ruby_top_self" @@ -231,44 +297,69 @@ class RDoc::Parser::C < RDoc::Parser source_file) end - @content.scan(%r{rb_define_attr\( - \s*([\w\.]+), - \s*"([^"]+)", - \s*(\d+), - \s*(\d+)\s*\); - }xm) do |var_name, attr_name, attr_reader, attr_writer| - #var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_attr(var_name, attr_name, - attr_reader.to_i != 0, - attr_writer.to_i != 0) - end - - @content.scan(%r{rb_define_global_function\s*\( + @content.scan(%r%rb_define_global_function\s*\( \s*"([^"]+)", \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, \s*(-?\w+)\s*\) (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - }xm) do |meth_name, meth_body, param_count, source_file| + %xm) do |meth_name, meth_body, param_count, source_file| handle_method("method", "rb_mKernel", meth_name, meth_body, param_count, source_file) end @content.scan(/define_filetest_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\)/xm) do - |meth_name, meth_body, param_count| + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, + \s*(-?\w+)\s*\)/xm) do |meth_name, meth_body, param_count| handle_method("method", "rb_mFileTest", meth_name, meth_body, param_count) handle_method("singleton_method", "rb_cFile", meth_name, meth_body, param_count) end end - def find_attr_comment(attr_name) - if @content =~ %r{((?>/\*.*?\*/\s+)) - rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi + ## + # Finds the comment for an alias on +class_name+ from +new_name+ to + # +old_name+ + + def find_alias_comment class_name, new_name, old_name + content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_alias\(\s*#{Regexp.escape class_name}\s*, + \s*"#{Regexp.escape new_name}"\s*, + \s*"#{Regexp.escape old_name}"\s*\);%xm + + $1 || '' + end + + ## + # Finds a comment for rb_define_attr, rb_attr or Document-attr. + # + # +var_name+ is the C class variable the attribute is defined on. + # +attr_name+ is the attribute's name. + # + # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or + # neither must be provided. + + def find_attr_comment var_name, attr_name, read = nil, write = nil + attr_name = Regexp.escape attr_name + + rw = if read and write then + /\s*#{read}\s*,\s*#{write}\s*/xm + else + /.*?/m + end + + if @content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_attr\((?:\s*#{var_name},)?\s* + "#{attr_name}"\s*, + #{rw}\)\s*;%xm then + $1 + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + rb_attr\(\s*#{var_name}\s*, + \s*#{attr_name}\s*, + #{rw},.*?\)\s*;%xm then $1 - elsif @content =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m + elsif @content =~ %r%Document-attr:\s#{attr_name}\s*?\n + ((?>.*?\*/))%xm then $1 else '' @@ -280,8 +371,10 @@ class RDoc::Parser::C < RDoc::Parser def find_body(class_name, meth_name, meth_obj, body, quiet = false) case body - when %r"((?>/\*.*?\*/\s*))((?:(?:static|SWIGINTERN)\s+)?(?:intern\s+)?VALUE\s+#{meth_name} - \s*(\([^)]*\))([^;]|$))"xm + when %r%((?>/\*.*?\*/\s*)) + ((?:(?:static|SWIGINTERN)\s+)? + (?:intern\s+)?VALUE\s+#{meth_name} + \s*(\([^)]*\))([^;]|$))%xm then comment = $1 body_text = $2 @@ -303,12 +396,13 @@ class RDoc::Parser::C < RDoc::Parser find_modifiers comment, meth_obj if comment + #meth_obj.params = params meth_obj.start_collecting_tokens tk = RDoc::RubyToken::Token.new nil, 1, 1 tk.set_text body_text meth_obj.add_token tk meth_obj.comment = strip_stars comment - when %r{((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+#{meth_name}\s+(\w+))}m + when %r%((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+#{meth_name}\s+(\w+))%m comment = $1 body_text = $2 find_body class_name, $3, meth_obj, body, true @@ -319,26 +413,29 @@ class RDoc::Parser::C < RDoc::Parser tk.set_text body_text meth_obj.add_token tk meth_obj.comment = strip_stars(comment) + meth_obj.comment.to_s - when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m + when %r%^\s*\#\s*define\s+#{meth_name}\s+(\w+)%m unless find_body(class_name, $1, meth_obj, body, true) - warn "No definition for #{meth_name}" unless @options.quiet + warn "No definition for #{meth_name}" if @options.verbosity > 1 return false end - else - # No body, but might still have an override comment - comment = find_override_comment(class_name, meth_obj.name) + else # No body, but might still have an override comment + comment = find_override_comment class_name, meth_obj.name if comment - find_modifiers(comment, meth_obj) + find_modifiers comment, meth_obj meth_obj.comment = strip_stars comment else - warn "No definition for #{meth_name}" unless @options.quiet + warn "No definition for #{meth_name}" if @options.verbosity > 1 return false end end + true end + ## + # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ + def find_class(raw_name, name) unless @classes[raw_name] if raw_name =~ /^rb_m/ @@ -382,16 +479,17 @@ class RDoc::Parser::C < RDoc::Parser def find_class_comment(class_name, class_mod) comment = nil - if @content =~ %r{ + if @content =~ %r% ((?>/\*.*?\*/\s+)) (static\s+)? void\s+ - Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)}xmi then # ) - comment = $1 - elsif @content =~ %r{Document-(?:class|module):\s+#{class_name}\s*?(?:<\s+[:,\w]+)?\n((?>.*?\*/))}m then + Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xmi then + comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') + elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? + (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then comment = $1 - elsif @content =~ %r{((?>/\*.*?\*/\s+)) - ([\w\.\s]+\s* = \s+)?rb_define_(class|module).*?"(#{class_name})"}xm then # " + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + ([\w\.\s]+\s* = \s+)?rb_define_(class|module).*?"(#{class_name})"%xm then comment = $1 end @@ -409,10 +507,13 @@ class RDoc::Parser::C < RDoc::Parser # comment or in the matching Document- section. def find_const_comment(type, const_name) - if @content =~ %r{((?>^\s*/\*.*?\*/\s+)) - rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi + if @content =~ %r%((?>^\s*/\*.*?\*/\s+)) + rb_define_#{type}\((?:\s*(\w+),)?\s* + "#{const_name}"\s*, + .*?\)\s*;%xmi then $1 - elsif @content =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m + elsif @content =~ %r%Document-(?:const|global|variable):\s#{const_name} + \s*?\n((?>.*?\*/))%xm $1 else '' @@ -420,56 +521,111 @@ class RDoc::Parser::C < RDoc::Parser end ## - # If the comment block contains a section that looks like: + # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. + # + # If <tt>:nodoc:</tt> is found, documentation on +meth_obj+ is suppressed. + # + # If <tt>:yields:</tt> is followed by an argument list it is used for the + # #block_params of +meth_obj+. + # + # If the comment block contains a <tt>call-seq:</tt> section like: + # + # call-seq: + # ARGF.readlines(sep=$/) -> array + # ARGF.readlines(limit) -> array + # ARGF.readlines(sep, limit) -> array # - # call-seq: - # Array.new - # Array.new(10) + # ARGF.to_a(sep=$/) -> array + # ARGF.to_a(limit) -> array + # ARGF.to_a(sep, limit) -> array # - # use it for the parameters. + # it is used for the parameters of +meth_obj+. + + def find_modifiers comment, meth_obj + # we must handle situations like the above followed by an unindented first + # comment. The difficulty is to make sure not to match lines starting + # with ARGF at the same indent, but that are after the first description + # paragraph. + + if comment =~ /call-seq:(.*?[^\s\*].*?)^\s*\*?\s*$/m then + all_start, all_stop = $~.offset(0) + seq_start, seq_stop = $~.offset(1) + + # we get the following lines that start with the leading word at the + # same indent, even if they have blank lines before + if $1 =~ /(^\s*\*?\s*\n)+^(\s*\*?\s*\w+)/m then + leading = $2 # ' * ARGF' in the example above + re = %r% + \A( + (^\s*\*?\s*\n)+ + (^#{Regexp.escape leading}.*?\n)+ + )+ + ^\s*\*?\s*$ + %xm + if comment[seq_stop..-1] =~ re then + all_stop = seq_stop + $~.offset(0).last + seq_stop = seq_stop + $~.offset(1).last + end + end - def find_modifiers(comment, meth_obj) - if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or - comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '') - meth_obj.document_self = false - end - if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or - comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') - seq = $1 - seq.gsub!(/^\s*\*\s*/, '') + seq = comment[seq_start..seq_stop] + seq.gsub!(/^(\s*\*?\s*?)(\S|\n)/m, '\2') + comment.slice! all_start...all_stop meth_obj.call_seq = seq + elsif comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') then + meth_obj.call_seq = $1.strip + end + + if comment.sub!(/\s*:(nodoc|doc|yields?|args?):\s*(.*)/, '') then + RDoc::Parser.process_directive meth_obj, $1, $2 end end + ## + # Finds a <tt>Document-method</tt> override for +meth_name+ in +class_name+ + def find_override_comment(class_name, meth_name) name = Regexp.escape(meth_name) - if @content =~ %r{Document-method:\s+#{class_name}(?:\.|::|#)#{name}\s*?\n((?>.*?\*/))}m then + + if @content =~ %r%Document-method:\s+#{class_name}(?:\.|::|#)#{name}\s*?\n((?>.*?\*/))%m then $1 - elsif @content =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m then + elsif @content =~ %r%Document-method:\s#{name}\s*?\n((?>.*?\*/))%m then $1 end end - def handle_attr(var_name, attr_name, reader, writer) + ## + # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either + # +read+, +write+ or both + + def handle_attr(var_name, attr_name, read, write) rw = '' - rw << 'R' if reader - rw << 'W' if writer + rw << 'R' if '1' == read + rw << 'W' if '1' == write class_name = @known_classes[var_name] return unless class_name - class_obj = find_class(var_name, class_name) + class_obj = find_class var_name, class_name + + return unless class_obj - if class_obj - comment = find_attr_comment(attr_name) - comment = strip_stars comment - att = RDoc::Attr.new '', attr_name, rw, comment - @stats.add_method att - class_obj.add_attribute(att) - end + comment = find_attr_comment var_name, attr_name + comment = strip_stars comment + + name = attr_name.gsub(/rb_intern\("([^"]+)"\)/, '\1') + + attr = RDoc::Attr.new '', name, rw, comment + + class_obj.add_attribute attr + @stats.add_attribute attr end + ## + # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ + # named +class_name+ in +parent+ which was assigned to the C +var_name+. + def handle_class_module(var_name, type, class_name, parent, in_module) parent_name = @known_classes[parent] || parent @@ -497,7 +653,7 @@ class RDoc::Parser::C < RDoc::Parser class_name end - if @content =~ %r{Document-class:\s+#{full_name}\s*<\s+([:,\w]+)} then + if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then parent_name = $1 end @@ -519,15 +675,14 @@ class RDoc::Parser::C < RDoc::Parser end ## - # Adds constant comments. By providing some_value: at the start ofthe - # comment you can override the C value of the comment to give a friendly - # definition. + # Adds constants. By providing some_value: at the start of the comment you + # can override the C value of the comment to give a friendly definition. # # /* 300: The perfect score in bowling */ # rb_define_const(cFoo, "PERFECT", INT2FIX(300); # - # Will override +INT2FIX(300)+ with the value +300+ in the output RDoc. - # Values may include quotes and escaped colons (\:). + # Will override <tt>INT2FIX(300)</tt> with the value +300+ in the output + # RDoc. Values may include quotes and escaped colons (\:). def handle_constants(type, var_name, const_name, definition) class_name = @known_classes[var_name] @@ -588,22 +743,35 @@ class RDoc::Parser::C < RDoc::Parser body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') end + ## + # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned + # to +var_name+. +type+ is the type of method definition function used. + # +singleton_method+ and +module_function+ create a singleton method. + def handle_method(type, var_name, meth_name, meth_body, param_count, source_file = nil) + singleton = false class_name = @known_classes[var_name] + unless class_name then + class_name = @singleton_classes[var_name] + singleton = true if class_name + end + return unless class_name class_obj = find_class var_name, class_name if class_obj then - if meth_name == "initialize" then - meth_name = "new" - type = "singleton_method" + if meth_name == 'initialize' then + meth_name = 'new' + singleton = true + type = 'method' # force public end meth_obj = RDoc::AnyMethod.new '', meth_name - meth_obj.singleton = %w[singleton_method module_function].include? type + meth_obj.singleton = + singleton || %w[singleton_method module_function].include?(type) p_count = Integer(param_count) rescue -1 @@ -627,7 +795,8 @@ class RDoc::Parser::C < RDoc::Parser body = @content end - if find_body(class_name, meth_body, meth_obj, body) and meth_obj.document_self then + if find_body(class_name, meth_body, meth_obj, body) and + meth_obj.document_self then class_obj.add_method meth_obj @stats.add_method meth_obj meth_obj.visibility = :private if 'private_method' == type @@ -635,13 +804,27 @@ class RDoc::Parser::C < RDoc::Parser end end + ## + # Registers a singleton class +sclass_var+ as a singleton of +class_var+ + + def handle_singleton sclass_var, class_var + class_name = @known_classes[class_var] + + @singleton_classes[sclass_var] = class_name + end + + ## + # Normalizes tabs in +body+ + def handle_tab_width(body) if /\t/ =~ body tab_width = @options.tab_width body.split(/\n/).map do |line| - 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #` + 1 while line.gsub!(/\t+/) do + ' ' * (tab_width * $&.length - $`.length % tab_width) + end && $~ line - end .join("\n") + end.join "\n" else body end @@ -654,7 +837,7 @@ class RDoc::Parser::C < RDoc::Parser # * :title: My Awesome Project # */ # - # This routine modifies it's parameter + # This routine modifies its parameter def look_for_directives_in(context, comment) preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include @@ -665,29 +848,33 @@ class RDoc::Parser::C < RDoc::Parser @options.main_page = param '' when 'title' then - @options.title = param + @options.default_title = param if @options.respond_to? :default_title= '' end end comment end + ## # Removes lines that are commented out that might otherwise get picked up # when scanning for classes and methods def remove_commented_out_lines - @content.gsub!(%r{//.*rb_define_}, '//') + @content.gsub!(%r%//.*rb_define_%, '//') end + ## + # Removes private comments from +comment+ + def remove_private_comments(comment) comment.gsub!(/\/?\*--\n(.*?)\/?\*\+\+/m, '') comment.sub!(/\/?\*--\n.*/m, '') end ## - # Extract the classes/modules and methods from a C file and return the - # corresponding top-level object + # Extracts the classes, modules, methods, attributes, constants and aliases + # from a C file and returns an RDoc::TopLevel for this file def scan remove_commented_out_lines @@ -696,6 +883,7 @@ class RDoc::Parser::C < RDoc::Parser do_methods do_includes do_aliases + do_attrs @top_level end |