diff --git a/ChangeLog b/ChangeLog index 0230ef6bd6..bca7f193bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +Mon Jan 1 06:13:11 2007 Eric Hodel + + * lib/rdoc/parsers/c_parser.rb: Make Rdoc accessible. Update constant + value information. + +Mon Jan 1 06:13:11 2007 Eric Hodel + + * ext/bigdecimal/bigdecimal.c: Update constant comments to provide + values for RDoc. + +Mon Jan 1 06:05:55 2007 Eric Hodel + + * lib/rdoc/parsers/parse_c.rb (RDoc::C_Parser#handle_constansts): + Allow RDoc comment to give friendly value for rb_define_const. Patch + by Daniel Berger , [ruby-patches-7499]. + * lib/rdoc/parsers/parse_c.rb (RDoc::C_Parser#handle_constansts): Fix + whitespace handling in constant comments. + Sun Dec 31 00:31:16 2006 Tadayoshi Funaba * lib/date.rb, lib/date/format.rb: updated based on date2 4.0. diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f10af59f5b..370f5ecd50 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1811,76 +1811,94 @@ Init_bigdecimal(void) /* Exceptions */ /* - * Determines whether overflow, underflow or zero divide result in + * 0xff: Determines whether overflow, underflow or zero divide result in * an exception being thrown. See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "EXCEPTION_ALL",INT2FIX(VP_EXCEPTION_ALL)); /* - * Determines what happens when the result of a computation is not a + * 0x02: Determines what happens when the result of a computation is not a * number (NaN). See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "EXCEPTION_NaN",INT2FIX(VP_EXCEPTION_NaN)); /* - * Determines what happens when the result of a computation is infinity. - * See BigDecimal.mode. + * 0x01: Determines what happens when the result of a computation is + * infinity. See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "EXCEPTION_INFINITY",INT2FIX(VP_EXCEPTION_INFINITY)); /* - * Determines what happens when the result of a computation is an underflow - * (a result too small to be represented). See BigDecimal.mode. + * 0x04: Determines what happens when the result of a computation is an + * underflow (a result too small to be represented). See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "EXCEPTION_UNDERFLOW",INT2FIX(VP_EXCEPTION_UNDERFLOW)); /* - * Determines what happens when the result of a computation is an underflow - * (a result too large to be represented). See BigDecimal.mode. + * 0x01: Determines what happens when the result of a computation is an + * underflow (a result too large to be represented). See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "EXCEPTION_OVERFLOW",INT2FIX(VP_EXCEPTION_OVERFLOW)); /* - * Determines what happens when a division by zero is performed. + * 0x01: Determines what happens when a division by zero is performed. * See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "EXCEPTION_ZERODIVIDE",INT2FIX(VP_EXCEPTION_ZERODIVIDE)); /* - * Determines what happens when a result must be rounded in order to - * fit in the appropriate number of significant digits. See + * 0x100: Determines what happens when a result must be rounded in order to + * fit in the appropriate number of significant digits. See * BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "ROUND_MODE",INT2FIX(VP_ROUND_MODE)); - /* Indicates that values should be rounded away from zero. See BigDecimal.mode. */ + /* 1: Indicates that values should be rounded away from zero. See + * BigDecimal.mode. + */ rb_define_const(rb_cBigDecimal, "ROUND_UP",INT2FIX(VP_ROUND_UP)); - /* Indicates that values should be rounded towards zero. See BigDecimal.mode. */ + + /* 2: Indicates that values should be rounded towards zero. See + * BigDecimal.mode. + */ rb_define_const(rb_cBigDecimal, "ROUND_DOWN",INT2FIX(VP_ROUND_DOWN)); - /* Indicates that digits >= 5 should be rounded up, others rounded down. See BigDecimal.mode. */ + + /* 3: Indicates that digits >= 5 should be rounded up, others rounded down. + * See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "ROUND_HALF_UP",INT2FIX(VP_ROUND_HALF_UP)); - /* Indicates that digits >= 6 should be rounded up, others rounded down. See BigDecimal.mode. */ + + /* 4: Indicates that digits >= 6 should be rounded up, others rounded down. + * See BigDecimal.mode. + */ rb_define_const(rb_cBigDecimal, "ROUND_HALF_DOWN",INT2FIX(VP_ROUND_HALF_DOWN)); - /* Round towards +infinity. See BigDecimal.mode. */ + /* 5: Round towards +infinity. See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "ROUND_CEILING",INT2FIX(VP_ROUND_CEIL)); - /* Round towards -infinity. See BigDecimal.mode. */ + + /* 6: Round towards -infinity. See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "ROUND_FLOOR",INT2FIX(VP_ROUND_FLOOR)); - /* Round towards the even neighbor. See BigDecimal.mode. */ + + /* 7: Round towards the even neighbor. See BigDecimal.mode. */ rb_define_const(rb_cBigDecimal, "ROUND_HALF_EVEN",INT2FIX(VP_ROUND_HALF_EVEN)); - /* Indicates that a value is not a number. See BigDecimal.sign. */ + /* 0: Indicates that a value is not a number. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_NaN",INT2FIX(VP_SIGN_NaN)); - /* Indicates that a value is +0. See BigDecimal.sign. */ + + /* 1: Indicates that a value is +0. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_POSITIVE_ZERO",INT2FIX(VP_SIGN_POSITIVE_ZERO)); - /* Indicates that a value is -0. See BigDecimal.sign. */ + + /* -1: Indicates that a value is -0. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_ZERO",INT2FIX(VP_SIGN_NEGATIVE_ZERO)); - /* Indicates that a value is positive and finite. See BigDecimal.sign. */ + + /* 2: Indicates that a value is positive and finite. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_POSITIVE_FINITE",INT2FIX(VP_SIGN_POSITIVE_FINITE)); - /* Indicates that a value is negative and finite. See BigDecimal.sign. */ + + /* -2: Indicates that a value is negative and finite. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_FINITE",INT2FIX(VP_SIGN_NEGATIVE_FINITE)); - /* Indicates that a value is positive and infinite. See BigDecimal.sign. */ + + /* 3: Indicates that a value is positive and infinite. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_POSITIVE_INFINITE",INT2FIX(VP_SIGN_POSITIVE_INFINITE)); - /* Indicates that a value is negative and infinite. See BigDecimal.sign. */ + + /* -3: Indicates that a value is negative and infinite. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_INFINITE",INT2FIX(VP_SIGN_NEGATIVE_INFINITE)); /* instance methods */ diff --git a/lib/rdoc/parsers/parse_c.rb b/lib/rdoc/parsers/parse_c.rb index 19b5d443f6..4ebd25c709 100644 --- a/lib/rdoc/parsers/parse_c.rb +++ b/lib/rdoc/parsers/parse_c.rb @@ -1,104 +1,16 @@ - # We attempt to parse C extension files. Basically we look for - # the standard patterns that you find in extensions: rb_define_class, - # rb_define_method and so on. We also try to find the corresponding - # C source for the methods and extract comments, but if we fail - # we don't worry too much. - # - # The comments associated with a Ruby method are extracted from the C - # comment block associated with the routine that _implements_ that - # method, that is to say the method whose name is given in the - # rb_define_method call. For example, you might write: - # - # /* - # * Returns a new array that is a one-dimensional flattening of this - # * array (recursively). That is, for every element that is an array, - # * extract its elements into the new array. - # * - # * s = [ 1, 2, 3 ] #=> [1, 2, 3] - # * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] - # * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] - # * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - # */ - # static VALUE - # rb_ary_flatten(ary) - # VALUE ary; - # { - # ary = rb_obj_dup(ary); - # rb_ary_flatten_bang(ary); - # return ary; - # } - # - # ... - # - # void - # Init_Array() - # { - # ... - # rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); - # - # Here RDoc will determine from the rb_define_method line that there's a - # method called "flatten" in class Array, and will look for the implementation - # in the method rb_ary_flatten. It will then use the comment from that - # method in the HTML output. This method must be in the same source file - # as the rb_define_method. - # - # C classes can be diagramed (see /tc/dl/ruby/ruby/error.c), and RDoc - # integrates C and Ruby source into one tree - # - # The comment blocks may include special direcives: - # - # [Document-class: name] - # This comment block is documentation for the given class. Use this - # when the Init_xxx method is not named after the class. - # - # [Document-method: name] - # This comment documents the named method. Use when RDoc cannot outomatically - # find the method from it's declaration - # - # [call-seq: text up to an empty line] - # Because C source doesn't give descripive names to Ruby-level parameters, - # you need to document the calling sequence explicitly - # - # In additon, RDoc assumes by default that the C method implementing a - # Ruby function is in the same source file as the rb_define_method call. - # If this isn't the case, add the comment - # - # rb_define_method(....); // in: filename - # - # As an example, we might have an extension that defines multiple classes - # in its Init_xxx method. We could document them using - # - # - # /* - # * Document-class: MyClass - # * - # * Encapsulate the writing and reading of the configuration - # * file. ... - # */ - # - # /* - # * Document-method: read_value - # * - # * call-seq: - # * cfg.read_value(key) -> value - # * cfg.read_value(key} { |key| } -> value - # * - # * Return the value corresponding to +key+ from the configuration. - # * In the second form, if the key isn't found, invoke the - # * block and return its value. - # */ - # - - - # Classes and modules built in to the interpreter. We need - # these to define superclasses of user objects +# Classes and modules built in to the interpreter. We need +# these to define superclasses of user objects require "rdoc/code_objects" require "rdoc/parsers/parserfactory" - +require "rdoc/options" +require "rdoc/rdoc" module RDoc + ## + # Ruby's built-in classes. + KNOWN_CLASSES = { "rb_cObject" => "Object", "rb_cArray" => "Array", @@ -158,13 +70,103 @@ module RDoc "rb_mGC" => "GC", "rb_mMath" => "Math", "rb_mProcess" => "Process" - } - # See rdoc/c_parse.rb + ## + # We attempt to parse C extension files. Basically we look for + # the standard patterns that you find in extensions: rb_define_class, + # rb_define_method and so on. We also try to find the corresponding + # C source for the methods and extract comments, but if we fail + # we don't worry too much. + # + # The comments associated with a Ruby method are extracted from the C + # comment block associated with the routine that _implements_ that + # method, that is to say the method whose name is given in the + # rb_define_method call. For example, you might write: + # + # /* + # * Returns a new array that is a one-dimensional flattening of this + # * array (recursively). That is, for every element that is an array, + # * extract its elements into the new array. + # * + # * s = [ 1, 2, 3 ] #=> [1, 2, 3] + # * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] + # * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] + # * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # */ + # static VALUE + # rb_ary_flatten(ary) + # VALUE ary; + # { + # ary = rb_obj_dup(ary); + # rb_ary_flatten_bang(ary); + # return ary; + # } + # + # ... + # + # void + # Init_Array() + # { + # ... + # rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); + # + # Here RDoc will determine from the rb_define_method line that there's a + # method called "flatten" in class Array, and will look for the implementation + # in the method rb_ary_flatten. It will then use the comment from that + # method in the HTML output. This method must be in the same source file + # as the rb_define_method. + # + # C classes can be diagramed (see /tc/dl/ruby/ruby/error.c), and RDoc + # integrates C and Ruby source into one tree + # + # The comment blocks may include special direcives: + # + # [Document-class: name] + # This comment block is documentation for the given class. Use this + # when the Init_xxx method is not named after the class. + # + # [Document-method: name] + # This comment documents the named method. Use when RDoc cannot + # automatically find the method from it's declaration + # + # [call-seq: text up to an empty line] + # Because C source doesn't give descripive names to Ruby-level parameters, + # you need to document the calling sequence explicitly + # + # In additon, RDoc assumes by default that the C method implementing a + # Ruby function is in the same source file as the rb_define_method call. + # If this isn't the case, add the comment + # + # rb_define_method(....); // in: filename + # + # As an example, we might have an extension that defines multiple classes + # in its Init_xxx method. We could document them using + # + # + # /* + # * Document-class: MyClass + # * + # * Encapsulate the writing and reading of the configuration + # * file. ... + # */ + # + # /* + # * Document-method: read_value + # * + # * call-seq: + # * cfg.read_value(key) -> value + # * cfg.read_value(key} { |key| } -> value + # * + # * Return the value corresponding to +key+ from the configuration. + # * In the second form, if the key isn't found, invoke the + # * block and return its value. + # */ + # class C_Parser + attr_accessor :progress extend ParserFactory parse_files_matching(/\.(c|cc|cpp|CC)$/) @@ -217,8 +219,9 @@ module RDoc comment.sub!(/\/?\*--.*/m, '') end - # remove lines that are commented out that might otherwise get - # picked up when scanning for classes and methods + ## + # removes lines that are commented out that might otherwise get picked up + # when scanning for classes and methods def remove_commented_out_lines @body.gsub!(%r{//.*rb_define_}, '//') @@ -260,7 +263,6 @@ module RDoc @classes[var_name] = cm @known_classes[var_name] = cm.full_name end - ############################################################ @@ -425,7 +427,16 @@ module RDoc end 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. + # + # /* 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 (\:). def handle_constants(type, var_name, const_name, definition) #@stats.num_constants += 1 @@ -442,14 +453,39 @@ module RDoc comment = find_const_comment(type, const_name) - con = Constant.new(const_name, definition, mangle_comment(comment)) + # In the case of rb_define_const, the definition and comment are in + # "/* definition: comment */" form. The literal ':' and '\' characters + # can be escaped with a backslash. + if type.downcase == 'const' then + elements = mangle_comment(comment).split(':') + if elements.nil? or elements.empty? then + con = Constant.new(const_name, definition, mangle_comment(comment)) + else + new_definition = elements[0..-2].join(':') + if new_definition.empty? then # Default to literal C definition + new_definition = definition + else + new_definition.gsub!("\:", ":") + new_definition.gsub!("\\", '\\') + end + new_definition.sub!(/\A(\s+)/, '') + new_comment = $1.nil? ? elements.last : "#{$1}#{elements.last.lstrip}" + con = Constant.new(const_name, new_definition, + mangle_comment(new_comment)) + end + else + con = Constant.new(const_name, definition, mangle_comment(comment)) + end + class_obj.add_constant(con) end - ########################################################### + ## + # Finds a comment matching +type+ and +const_name+ either above the + # comment or in the matching Document- section. def find_const_comment(type, const_name) - if @body =~ %r{((?>/\*.*?\*/\s+)) + if @body =~ %r{((?>^\s*/\*.*?\*/\s+)) rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi $1 elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m @@ -610,13 +646,15 @@ module RDoc end - ################################################## - # - # If the comment block contains a section that looks like + ## + # If the comment block contains a section that looks like: + # # call-seq: # Array.new # Array.new(10) - # use it for the parameters + # + # use it for the parameters. + def find_modifiers(comment, meth_obj) if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '') @@ -639,10 +677,11 @@ module RDoc end end - ############################################################ - - # Look for includes of the form + ## + # Look for includes of the form: + # # rb_include_module(rb_cArray, rb_mEnumerable); + def do_includes @body.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| if cls = @classes[c] @@ -652,8 +691,7 @@ module RDoc end end - ############################################################ - + ## # Remove the /*'s and leading asterisks from C comments def mangle_comment(comment) @@ -686,7 +724,8 @@ module RDoc end end - # Remove #ifdefs that would otherwise confuse us + ## + # Removes #ifdefs that would otherwise confuse us def handle_ifdefs_in(body) body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 } @@ -695,3 +734,4 @@ module RDoc end end + diff --git a/test/rdoc/parsers/test_parse_c.rb b/test/rdoc/parsers/test_parse_c.rb new file mode 100644 index 0000000000..c7815a95c0 --- /dev/null +++ b/test/rdoc/parsers/test_parse_c.rb @@ -0,0 +1,148 @@ +require 'pp' +require 'stringio' +require 'tempfile' +require 'test/unit' +require 'rdoc/parsers/parse_c' + +class RDoc::C_Parser + attr_accessor :classes + + public :do_classes, :do_constants +end + +class TestRdocC_Parser < Test::Unit::TestCase + + def setup + @tempfile = Tempfile.new self.class.name + filename = @tempfile.path + + @top_level = RDoc::TopLevel.new filename + @fn = filename + @options = Options.instance + @stats = RDoc::Stats.new + + @progress = StringIO.new + end + + def teardown + @tempfile.unlink + end + + def test_do_constants + content = <<-EOF +#include + +void Init_foo(){ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* 300: The highest possible score in bowling */ + rb_define_const(cFoo, "PERFECT", INT2FIX(300)); + + /* Huzzah!: What you cheer when you roll a perfect game */ + rb_define_const(cFoo, "CHEER", rb_str_new2("Huzzah!")); + + /* TEST\:TEST: Checking to see if escaped semicolon works */ + rb_define_const(cFoo, "TEST", rb_str_new2("TEST:TEST")); + + /* \\: The file separator on MS Windows */ + rb_define_const(cFoo, "MSEPARATOR", rb_str_new2("\\")); + + /* /: The file separator on Unix */ + rb_define_const(cFoo, "SEPARATOR", rb_str_new2("/")); + + /* C:\\Program Files\\Stuff: A directory on MS Windows */ + rb_define_const(cFoo, "STUFF", rb_str_new2("C:\\Program Files\\Stuff")); + + /* Default definition */ + rb_define_const(cFoo, "NOSEMI", INT2FIX(99)); + + rb_define_const(cFoo, "NOCOMMENT", rb_str_new2("No comment")); + + /* + * Multiline comment goes here because this comment spans multiple lines. + * Multiline comment goes here because this comment spans multiple lines. + */ + rb_define_const(cFoo, "MULTILINE", INT2FIX(1)); + + /* + * 1: Multiline comment goes here because this comment spans multiple lines. + * Multiline comment goes here because this comment spans multiple lines. + */ + rb_define_const(cFoo, "MULTILINE_VALUE", INT2FIX(1)); + + /* Multiline comment goes here because this comment spans multiple lines. + * Multiline comment goes here because this comment spans multiple lines. + */ + rb_define_const(cFoo, "MULTILINE_NOT_EMPTY", INT2FIX(1)); + +} + EOF + + parser = util_parser content + + parser.do_classes + parser.do_constants + + klass = parser.classes['cFoo'] + assert klass + + constants = klass.constants + assert !klass.constants.empty? + + constants = constants.map { |c| [c.name, c.value, c.comment] } + + assert_equal ['PERFECT', '300', + "\n The highest possible score in bowling \n "], + constants.shift + assert_equal ['CHEER', 'Huzzah!', + "\n What you cheer when you roll a perfect game \n "], + constants.shift + assert_equal ['TEST', 'TEST:TEST', + "\n Checking to see if escaped semicolon works \n "], + constants.shift + assert_equal ['MSEPARATOR', '\\', + "\n The file separator on MS Windows \n "], + constants.shift + assert_equal ['SEPARATOR', '/', + "\n The file separator on Unix \n "], + constants.shift + assert_equal ['STUFF', 'C:\\Program Files\\Stuff', + "\n A directory on MS Windows \n "], + constants.shift + assert_equal ['NOSEMI', 'INT2FIX(99)', + "\n Default definition \n "], + constants.shift + assert_equal ['NOCOMMENT', 'rb_str_new2("No comment")', nil], + constants.shift + + comment = <<-EOF.chomp + + + Multiline comment goes here because this comment spans multiple lines. + Multiline comment goes here because this comment spans multiple lines. + + + EOF + assert_equal ['MULTILINE', 'INT2FIX(1)', comment], constants.shift + assert_equal ['MULTILINE_VALUE', '1', comment], constants.shift + + comment = <<-EOF.chomp + + Multiline comment goes here because this comment spans multiple lines. + Multiline comment goes here because this comment spans multiple lines. + + + EOF + assert_equal ['MULTILINE_NOT_EMPTY', 'INT2FIX(1)', comment], constants.shift + + assert constants.empty?, constants.inspect + end + + def util_parser(content) + parser = RDoc::C_Parser.new @top_level, @fn, content, @options, @stats + parser.progress = @progress + parser + end + +end +