1 | # Classes and modules built in to the interpreter. We need
|
---|
2 | # these to define superclasses of user objects
|
---|
3 |
|
---|
4 | require "rdoc/code_objects"
|
---|
5 | require "rdoc/parsers/parserfactory"
|
---|
6 | require "rdoc/options"
|
---|
7 | require "rdoc/rdoc"
|
---|
8 |
|
---|
9 | module RDoc
|
---|
10 |
|
---|
11 | ##
|
---|
12 | # Ruby's built-in classes.
|
---|
13 |
|
---|
14 | KNOWN_CLASSES = {
|
---|
15 | "rb_cObject" => "Object",
|
---|
16 | "rb_cArray" => "Array",
|
---|
17 | "rb_cBignum" => "Bignum",
|
---|
18 | "rb_cClass" => "Class",
|
---|
19 | "rb_cDir" => "Dir",
|
---|
20 | "rb_cData" => "Data",
|
---|
21 | "rb_cFalseClass" => "FalseClass",
|
---|
22 | "rb_cFile" => "File",
|
---|
23 | "rb_cFixnum" => "Fixnum",
|
---|
24 | "rb_cFloat" => "Float",
|
---|
25 | "rb_cHash" => "Hash",
|
---|
26 | "rb_cInteger" => "Integer",
|
---|
27 | "rb_cIO" => "IO",
|
---|
28 | "rb_cModule" => "Module",
|
---|
29 | "rb_cNilClass" => "NilClass",
|
---|
30 | "rb_cNumeric" => "Numeric",
|
---|
31 | "rb_cProc" => "Proc",
|
---|
32 | "rb_cRange" => "Range",
|
---|
33 | "rb_cRegexp" => "Regexp",
|
---|
34 | "rb_cString" => "String",
|
---|
35 | "rb_cSymbol" => "Symbol",
|
---|
36 | "rb_cThread" => "Thread",
|
---|
37 | "rb_cTime" => "Time",
|
---|
38 | "rb_cTrueClass" => "TrueClass",
|
---|
39 | "rb_cStruct" => "Struct",
|
---|
40 | "rb_eException" => "Exception",
|
---|
41 | "rb_eStandardError" => "StandardError",
|
---|
42 | "rb_eSystemExit" => "SystemExit",
|
---|
43 | "rb_eInterrupt" => "Interrupt",
|
---|
44 | "rb_eSignal" => "Signal",
|
---|
45 | "rb_eFatal" => "Fatal",
|
---|
46 | "rb_eArgError" => "ArgError",
|
---|
47 | "rb_eEOFError" => "EOFError",
|
---|
48 | "rb_eIndexError" => "IndexError",
|
---|
49 | "rb_eRangeError" => "RangeError",
|
---|
50 | "rb_eIOError" => "IOError",
|
---|
51 | "rb_eRuntimeError" => "RuntimeError",
|
---|
52 | "rb_eSecurityError" => "SecurityError",
|
---|
53 | "rb_eSystemCallError" => "SystemCallError",
|
---|
54 | "rb_eTypeError" => "TypeError",
|
---|
55 | "rb_eZeroDivError" => "ZeroDivError",
|
---|
56 | "rb_eNotImpError" => "NotImpError",
|
---|
57 | "rb_eNoMemError" => "NoMemError",
|
---|
58 | "rb_eFloatDomainError" => "FloatDomainError",
|
---|
59 | "rb_eScriptError" => "ScriptError",
|
---|
60 | "rb_eNameError" => "NameError",
|
---|
61 | "rb_eSyntaxError" => "SyntaxError",
|
---|
62 | "rb_eLoadError" => "LoadError",
|
---|
63 |
|
---|
64 | "rb_mKernel" => "Kernel",
|
---|
65 | "rb_mComparable" => "Comparable",
|
---|
66 | "rb_mEnumerable" => "Enumerable",
|
---|
67 | "rb_mPrecision" => "Precision",
|
---|
68 | "rb_mErrno" => "Errno",
|
---|
69 | "rb_mFileTest" => "FileTest",
|
---|
70 | "rb_mGC" => "GC",
|
---|
71 | "rb_mMath" => "Math",
|
---|
72 | "rb_mProcess" => "Process"
|
---|
73 | }
|
---|
74 |
|
---|
75 | ##
|
---|
76 | # We attempt to parse C extension files. Basically we look for
|
---|
77 | # the standard patterns that you find in extensions: <tt>rb_define_class,
|
---|
78 | # rb_define_method</tt> and so on. We also try to find the corresponding
|
---|
79 | # C source for the methods and extract comments, but if we fail
|
---|
80 | # we don't worry too much.
|
---|
81 | #
|
---|
82 | # The comments associated with a Ruby method are extracted from the C
|
---|
83 | # comment block associated with the routine that _implements_ that
|
---|
84 | # method, that is to say the method whose name is given in the
|
---|
85 | # <tt>rb_define_method</tt> call. For example, you might write:
|
---|
86 | #
|
---|
87 | # /*
|
---|
88 | # * Returns a new array that is a one-dimensional flattening of this
|
---|
89 | # * array (recursively). That is, for every element that is an array,
|
---|
90 | # * extract its elements into the new array.
|
---|
91 | # *
|
---|
92 | # * s = [ 1, 2, 3 ] #=> [1, 2, 3]
|
---|
93 | # * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]]
|
---|
94 | # * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
|
---|
95 | # * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
---|
96 | # */
|
---|
97 | # static VALUE
|
---|
98 | # rb_ary_flatten(ary)
|
---|
99 | # VALUE ary;
|
---|
100 | # {
|
---|
101 | # ary = rb_obj_dup(ary);
|
---|
102 | # rb_ary_flatten_bang(ary);
|
---|
103 | # return ary;
|
---|
104 | # }
|
---|
105 | #
|
---|
106 | # ...
|
---|
107 | #
|
---|
108 | # void
|
---|
109 | # Init_Array()
|
---|
110 | # {
|
---|
111 | # ...
|
---|
112 | # rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0);
|
---|
113 | #
|
---|
114 | # Here RDoc will determine from the rb_define_method line that there's a
|
---|
115 | # method called "flatten" in class Array, and will look for the implementation
|
---|
116 | # in the method rb_ary_flatten. It will then use the comment from that
|
---|
117 | # method in the HTML output. This method must be in the same source file
|
---|
118 | # as the rb_define_method.
|
---|
119 | #
|
---|
120 | # C classes can be diagramed (see /tc/dl/ruby/ruby/error.c), and RDoc
|
---|
121 | # integrates C and Ruby source into one tree
|
---|
122 | #
|
---|
123 | # The comment blocks may include special direcives:
|
---|
124 | #
|
---|
125 | # [Document-class: <i>name</i>]
|
---|
126 | # This comment block is documentation for the given class. Use this
|
---|
127 | # when the <tt>Init_xxx</tt> method is not named after the class.
|
---|
128 | #
|
---|
129 | # [Document-method: <i>name</i>]
|
---|
130 | # This comment documents the named method. Use when RDoc cannot
|
---|
131 | # automatically find the method from it's declaration
|
---|
132 | #
|
---|
133 | # [call-seq: <i>text up to an empty line</i>]
|
---|
134 | # Because C source doesn't give descripive names to Ruby-level parameters,
|
---|
135 | # you need to document the calling sequence explicitly
|
---|
136 | #
|
---|
137 | # In additon, RDoc assumes by default that the C method implementing a
|
---|
138 | # Ruby function is in the same source file as the rb_define_method call.
|
---|
139 | # If this isn't the case, add the comment
|
---|
140 | #
|
---|
141 | # rb_define_method(....); // in: filename
|
---|
142 | #
|
---|
143 | # As an example, we might have an extension that defines multiple classes
|
---|
144 | # in its Init_xxx method. We could document them using
|
---|
145 | #
|
---|
146 | #
|
---|
147 | # /*
|
---|
148 | # * Document-class: MyClass
|
---|
149 | # *
|
---|
150 | # * Encapsulate the writing and reading of the configuration
|
---|
151 | # * file. ...
|
---|
152 | # */
|
---|
153 | #
|
---|
154 | # /*
|
---|
155 | # * Document-method: read_value
|
---|
156 | # *
|
---|
157 | # * call-seq:
|
---|
158 | # * cfg.read_value(key) -> value
|
---|
159 | # * cfg.read_value(key} { |key| } -> value
|
---|
160 | # *
|
---|
161 | # * Return the value corresponding to +key+ from the configuration.
|
---|
162 | # * In the second form, if the key isn't found, invoke the
|
---|
163 | # * block and return its value.
|
---|
164 | # */
|
---|
165 | #
|
---|
166 |
|
---|
167 | class C_Parser
|
---|
168 |
|
---|
169 | attr_accessor :progress
|
---|
170 |
|
---|
171 | extend ParserFactory
|
---|
172 | parse_files_matching(/\.(c|cc|cpp|CC)$/)
|
---|
173 |
|
---|
174 | @@known_bodies = {}
|
---|
175 |
|
---|
176 | # prepare to parse a C file
|
---|
177 | def initialize(top_level, file_name, body, options, stats)
|
---|
178 | @known_classes = KNOWN_CLASSES.dup
|
---|
179 | @body = handle_tab_width(handle_ifdefs_in(body))
|
---|
180 | @options = options
|
---|
181 | @stats = stats
|
---|
182 | @top_level = top_level
|
---|
183 | @classes = Hash.new
|
---|
184 | @file_dir = File.dirname(file_name)
|
---|
185 | @progress = $stderr unless options.quiet
|
---|
186 | end
|
---|
187 |
|
---|
188 | # Extract the classes/modules and methods from a C file
|
---|
189 | # and return the corresponding top-level object
|
---|
190 | def scan
|
---|
191 | remove_commented_out_lines
|
---|
192 | do_classes
|
---|
193 | do_constants
|
---|
194 | do_methods
|
---|
195 | do_includes
|
---|
196 | do_aliases
|
---|
197 | @top_level
|
---|
198 | end
|
---|
199 |
|
---|
200 | #######
|
---|
201 | private
|
---|
202 | #######
|
---|
203 |
|
---|
204 | def progress(char)
|
---|
205 | unless @options.quiet
|
---|
206 | @progress.print(char)
|
---|
207 | @progress.flush
|
---|
208 | end
|
---|
209 | end
|
---|
210 |
|
---|
211 | def warn(msg)
|
---|
212 | $stderr.puts
|
---|
213 | $stderr.puts msg
|
---|
214 | $stderr.flush
|
---|
215 | end
|
---|
216 |
|
---|
217 | def remove_private_comments(comment)
|
---|
218 | comment.gsub!(/\/?\*--(.*?)\/?\*\+\+/m, '')
|
---|
219 | comment.sub!(/\/?\*--.*/m, '')
|
---|
220 | end
|
---|
221 |
|
---|
222 | ##
|
---|
223 | # removes lines that are commented out that might otherwise get picked up
|
---|
224 | # when scanning for classes and methods
|
---|
225 |
|
---|
226 | def remove_commented_out_lines
|
---|
227 | @body.gsub!(%r{//.*rb_define_}, '//')
|
---|
228 | end
|
---|
229 |
|
---|
230 | def handle_class_module(var_name, class_mod, class_name, parent, in_module)
|
---|
231 | progress(class_mod[0, 1])
|
---|
232 |
|
---|
233 | parent_name = @known_classes[parent] || parent
|
---|
234 |
|
---|
235 | if in_module
|
---|
236 | enclosure = @classes[in_module]
|
---|
237 | unless enclosure
|
---|
238 | if enclosure = @known_classes[in_module]
|
---|
239 | handle_class_module(in_module, (/^rb_m/ =~ in_module ? "module" : "class"),
|
---|
240 | enclosure, nil, nil)
|
---|
241 | enclosure = @classes[in_module]
|
---|
242 | end
|
---|
243 | end
|
---|
244 | unless enclosure
|
---|
245 | warn("Enclosing class/module '#{in_module}' for " +
|
---|
246 | "#{class_mod} #{class_name} not known")
|
---|
247 | return
|
---|
248 | end
|
---|
249 | else
|
---|
250 | enclosure = @top_level
|
---|
251 | end
|
---|
252 |
|
---|
253 | if class_mod == "class"
|
---|
254 | cm = enclosure.add_class(NormalClass, class_name, parent_name)
|
---|
255 | @stats.num_classes += 1
|
---|
256 | else
|
---|
257 | cm = enclosure.add_module(NormalModule, class_name)
|
---|
258 | @stats.num_modules += 1
|
---|
259 | end
|
---|
260 | cm.record_location(enclosure.toplevel)
|
---|
261 |
|
---|
262 | find_class_comment(cm.full_name, cm)
|
---|
263 | @classes[var_name] = cm
|
---|
264 | @known_classes[var_name] = cm.full_name
|
---|
265 | end
|
---|
266 |
|
---|
267 | ##
|
---|
268 | # Look for class or module documentation above Init_+class_name+(void),
|
---|
269 | # in a Document-class +class_name+ (or module) comment or above an
|
---|
270 | # rb_define_class (or module). If a comment is supplied above a matching
|
---|
271 | # Init_ and a rb_define_class the Init_ comment is used.
|
---|
272 | #
|
---|
273 | # /*
|
---|
274 | # * This is a comment for Foo
|
---|
275 | # */
|
---|
276 | # Init_Foo(void) {
|
---|
277 | # VALUE cFoo = rb_define_class("Foo", rb_cObject);
|
---|
278 | # }
|
---|
279 | #
|
---|
280 | # /*
|
---|
281 | # * Document-class: Foo
|
---|
282 | # * This is a comment for Foo
|
---|
283 | # */
|
---|
284 | # Init_foo(void) {
|
---|
285 | # VALUE cFoo = rb_define_class("Foo", rb_cObject);
|
---|
286 | # }
|
---|
287 | #
|
---|
288 | # /*
|
---|
289 | # * This is a comment for Foo
|
---|
290 | # */
|
---|
291 | # VALUE cFoo = rb_define_class("Foo", rb_cObject);
|
---|
292 |
|
---|
293 | def find_class_comment(class_name, class_meth)
|
---|
294 | comment = nil
|
---|
295 | if @body =~ %r{((?>/\*.*?\*/\s+))
|
---|
296 | (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)}xmi
|
---|
297 | comment = $1
|
---|
298 | elsif @body =~ %r{Document-(class|module):\s#{class_name}\s*?\n((?>.*?\*/))}m
|
---|
299 | comment = $2
|
---|
300 | else
|
---|
301 | if @body =~ /rb_define_(class|module)/m then
|
---|
302 | class_name = class_name.split("::").last
|
---|
303 | comments = []
|
---|
304 | @body.split(/(\/\*.*?\*\/)\s*?\n/m).each_with_index do |chunk, index|
|
---|
305 | comments[index] = chunk
|
---|
306 | if chunk =~ /rb_define_(class|module).*?"(#{class_name})"/m then
|
---|
307 | comment = comments[index-1]
|
---|
308 | break
|
---|
309 | end
|
---|
310 | end
|
---|
311 | end
|
---|
312 | end
|
---|
313 | class_meth.comment = mangle_comment(comment) if comment
|
---|
314 | end
|
---|
315 |
|
---|
316 | ############################################################
|
---|
317 |
|
---|
318 | def do_classes
|
---|
319 | @body.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do
|
---|
320 | |var_name, class_name|
|
---|
321 | handle_class_module(var_name, "module", class_name, nil, nil)
|
---|
322 | end
|
---|
323 |
|
---|
324 | # The '.' lets us handle SWIG-generated files
|
---|
325 | @body.scan(/([\w\.]+)\s* = \s*rb_define_class\s*
|
---|
326 | \(
|
---|
327 | \s*"(\w+)",
|
---|
328 | \s*(\w+)\s*
|
---|
329 | \)/mx) do
|
---|
330 |
|
---|
331 | |var_name, class_name, parent|
|
---|
332 | handle_class_module(var_name, "class", class_name, parent, nil)
|
---|
333 | end
|
---|
334 |
|
---|
335 | @body.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do
|
---|
336 | |var_name, class_name, parent|
|
---|
337 | parent = nil if parent == "0"
|
---|
338 | handle_class_module(var_name, "class", class_name, parent, nil)
|
---|
339 | end
|
---|
340 |
|
---|
341 | @body.scan(/(\w+)\s* = \s*rb_define_module_under\s*
|
---|
342 | \(
|
---|
343 | \s*(\w+),
|
---|
344 | \s*"(\w+)"
|
---|
345 | \s*\)/mx) do
|
---|
346 |
|
---|
347 | |var_name, in_module, class_name|
|
---|
348 | handle_class_module(var_name, "module", class_name, nil, in_module)
|
---|
349 | end
|
---|
350 |
|
---|
351 | @body.scan(/([\w\.]+)\s* = \s*rb_define_class_under\s*
|
---|
352 | \(
|
---|
353 | \s*(\w+),
|
---|
354 | \s*"(\w+)",
|
---|
355 | \s*(\w+)\s*
|
---|
356 | \s*\)/mx) do
|
---|
357 |
|
---|
358 | |var_name, in_module, class_name, parent|
|
---|
359 | handle_class_module(var_name, "class", class_name, parent, in_module)
|
---|
360 | end
|
---|
361 |
|
---|
362 | end
|
---|
363 |
|
---|
364 | ###########################################################
|
---|
365 |
|
---|
366 | def do_constants
|
---|
367 | @body.scan(%r{\Wrb_define_
|
---|
368 | (
|
---|
369 | variable |
|
---|
370 | readonly_variable |
|
---|
371 | const |
|
---|
372 | global_const |
|
---|
373 | )
|
---|
374 | \s*\(
|
---|
375 | (?:\s*(\w+),)?
|
---|
376 | \s*"(\w+)",
|
---|
377 | \s*(.*?)\s*\)\s*;
|
---|
378 | }xm) do
|
---|
379 |
|
---|
380 | |type, var_name, const_name, definition|
|
---|
381 | var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel"
|
---|
382 | handle_constants(type, var_name, const_name, definition)
|
---|
383 | end
|
---|
384 | end
|
---|
385 |
|
---|
386 | ############################################################
|
---|
387 |
|
---|
388 | def do_methods
|
---|
389 |
|
---|
390 | @body.scan(%r{rb_define_
|
---|
391 | (
|
---|
392 | singleton_method |
|
---|
393 | method |
|
---|
394 | module_function |
|
---|
395 | private_method
|
---|
396 | )
|
---|
397 | \s*\(\s*([\w\.]+),
|
---|
398 | \s*"([^"]+)",
|
---|
399 | \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
|
---|
400 | \s*(-?\w+)\s*\)
|
---|
401 | (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))?
|
---|
402 | }xm) do
|
---|
403 | |type, var_name, meth_name, meth_body, param_count, source_file|
|
---|
404 | #"
|
---|
405 |
|
---|
406 | # Ignore top-object and weird struct.c dynamic stuff
|
---|
407 | next if var_name == "ruby_top_self"
|
---|
408 | next if var_name == "nstr"
|
---|
409 | next if var_name == "envtbl"
|
---|
410 | next if var_name == "argf" # it'd be nice to handle this one
|
---|
411 |
|
---|
412 | var_name = "rb_cObject" if var_name == "rb_mKernel"
|
---|
413 | handle_method(type, var_name, meth_name,
|
---|
414 | meth_body, param_count, source_file)
|
---|
415 | end
|
---|
416 |
|
---|
417 | @body.scan(%r{rb_define_attr\(
|
---|
418 | \s*([\w\.]+),
|
---|
419 | \s*"([^"]+)",
|
---|
420 | \s*(\d+),
|
---|
421 | \s*(\d+)\s*\);
|
---|
422 | }xm) do #"
|
---|
423 | |var_name, attr_name, attr_reader, attr_writer|
|
---|
424 |
|
---|
425 | #var_name = "rb_cObject" if var_name == "rb_mKernel"
|
---|
426 | handle_attr(var_name, attr_name,
|
---|
427 | attr_reader.to_i != 0,
|
---|
428 | attr_writer.to_i != 0)
|
---|
429 | end
|
---|
430 |
|
---|
431 | @body.scan(%r{rb_define_global_function\s*\(
|
---|
432 | \s*"([^"]+)",
|
---|
433 | \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
|
---|
434 | \s*(-?\w+)\s*\)
|
---|
435 | (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))?
|
---|
436 | }xm) do #"
|
---|
437 | |meth_name, meth_body, param_count, source_file|
|
---|
438 | handle_method("method", "rb_mKernel", meth_name,
|
---|
439 | meth_body, param_count, source_file)
|
---|
440 | end
|
---|
441 |
|
---|
442 | @body.scan(/define_filetest_function\s*\(
|
---|
443 | \s*"([^"]+)",
|
---|
444 | \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
|
---|
445 | \s*(-?\w+)\s*\)/xm) do #"
|
---|
446 | |meth_name, meth_body, param_count|
|
---|
447 |
|
---|
448 | handle_method("method", "rb_mFileTest", meth_name, meth_body, param_count)
|
---|
449 | handle_method("singleton_method", "rb_cFile", meth_name, meth_body, param_count)
|
---|
450 | end
|
---|
451 | end
|
---|
452 |
|
---|
453 | ############################################################
|
---|
454 |
|
---|
455 | def do_aliases
|
---|
456 | @body.scan(%r{rb_define_alias\s*\(\s*(\w+),\s*"([^"]+)",\s*"([^"]+)"\s*\)}m) do
|
---|
457 | |var_name, new_name, old_name|
|
---|
458 | @stats.num_methods += 1
|
---|
459 | class_name = @known_classes[var_name] || var_name
|
---|
460 | class_obj = find_class(var_name, class_name)
|
---|
461 |
|
---|
462 | class_obj.add_alias(Alias.new("", old_name, new_name, ""))
|
---|
463 | end
|
---|
464 | end
|
---|
465 |
|
---|
466 | ##
|
---|
467 | # Adds constant comments. By providing some_value: at the start ofthe
|
---|
468 | # comment you can override the C value of the comment to give a friendly
|
---|
469 | # definition.
|
---|
470 | #
|
---|
471 | # /* 300: The perfect score in bowling */
|
---|
472 | # rb_define_const(cFoo, "PERFECT", INT2FIX(300);
|
---|
473 | #
|
---|
474 | # Will override +INT2FIX(300)+ with the value +300+ in the output RDoc.
|
---|
475 | # Values may include quotes and escaped colons (\:).
|
---|
476 |
|
---|
477 | def handle_constants(type, var_name, const_name, definition)
|
---|
478 | #@stats.num_constants += 1
|
---|
479 | class_name = @known_classes[var_name]
|
---|
480 |
|
---|
481 | return unless class_name
|
---|
482 |
|
---|
483 | class_obj = find_class(var_name, class_name)
|
---|
484 |
|
---|
485 | unless class_obj
|
---|
486 | warn("Enclosing class/module '#{const_name}' for not known")
|
---|
487 | return
|
---|
488 | end
|
---|
489 |
|
---|
490 | comment = find_const_comment(type, const_name)
|
---|
491 |
|
---|
492 | # In the case of rb_define_const, the definition and comment are in
|
---|
493 | # "/* definition: comment */" form. The literal ':' and '\' characters
|
---|
494 | # can be escaped with a backslash.
|
---|
495 | if type.downcase == 'const' then
|
---|
496 | elements = mangle_comment(comment).split(':')
|
---|
497 | if elements.nil? or elements.empty? then
|
---|
498 | con = Constant.new(const_name, definition, mangle_comment(comment))
|
---|
499 | else
|
---|
500 | new_definition = elements[0..-2].join(':')
|
---|
501 | if new_definition.empty? then # Default to literal C definition
|
---|
502 | new_definition = definition
|
---|
503 | else
|
---|
504 | new_definition.gsub!("\:", ":")
|
---|
505 | new_definition.gsub!("\\", '\\')
|
---|
506 | end
|
---|
507 | new_definition.sub!(/\A(\s+)/, '')
|
---|
508 | new_comment = $1.nil? ? elements.last : "#{$1}#{elements.last.lstrip}"
|
---|
509 | con = Constant.new(const_name, new_definition,
|
---|
510 | mangle_comment(new_comment))
|
---|
511 | end
|
---|
512 | else
|
---|
513 | con = Constant.new(const_name, definition, mangle_comment(comment))
|
---|
514 | end
|
---|
515 |
|
---|
516 | class_obj.add_constant(con)
|
---|
517 | end
|
---|
518 |
|
---|
519 | ##
|
---|
520 | # Finds a comment matching +type+ and +const_name+ either above the
|
---|
521 | # comment or in the matching Document- section.
|
---|
522 |
|
---|
523 | def find_const_comment(type, const_name)
|
---|
524 | if @body =~ %r{((?>^\s*/\*.*?\*/\s+))
|
---|
525 | rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi
|
---|
526 | $1
|
---|
527 | elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m
|
---|
528 | $1
|
---|
529 | else
|
---|
530 | ''
|
---|
531 | end
|
---|
532 | end
|
---|
533 |
|
---|
534 | ###########################################################
|
---|
535 |
|
---|
536 | def handle_attr(var_name, attr_name, reader, writer)
|
---|
537 | rw = ''
|
---|
538 | if reader
|
---|
539 | #@stats.num_methods += 1
|
---|
540 | rw << 'R'
|
---|
541 | end
|
---|
542 | if writer
|
---|
543 | #@stats.num_methods += 1
|
---|
544 | rw << 'W'
|
---|
545 | end
|
---|
546 |
|
---|
547 | class_name = @known_classes[var_name]
|
---|
548 |
|
---|
549 | return unless class_name
|
---|
550 |
|
---|
551 | class_obj = find_class(var_name, class_name)
|
---|
552 |
|
---|
553 | if class_obj
|
---|
554 | comment = find_attr_comment(attr_name)
|
---|
555 | unless comment.empty?
|
---|
556 | comment = mangle_comment(comment)
|
---|
557 | end
|
---|
558 | att = Attr.new('', attr_name, rw, comment)
|
---|
559 | class_obj.add_attribute(att)
|
---|
560 | end
|
---|
561 |
|
---|
562 | end
|
---|
563 |
|
---|
564 | ###########################################################
|
---|
565 |
|
---|
566 | def find_attr_comment(attr_name)
|
---|
567 | if @body =~ %r{((?>/\*.*?\*/\s+))
|
---|
568 | rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi
|
---|
569 | $1
|
---|
570 | elsif @body =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m
|
---|
571 | $1
|
---|
572 | else
|
---|
573 | ''
|
---|
574 | end
|
---|
575 | end
|
---|
576 |
|
---|
577 | ###########################################################
|
---|
578 |
|
---|
579 | def handle_method(type, var_name, meth_name,
|
---|
580 | meth_body, param_count, source_file = nil)
|
---|
581 | progress(".")
|
---|
582 |
|
---|
583 | @stats.num_methods += 1
|
---|
584 | class_name = @known_classes[var_name]
|
---|
585 |
|
---|
586 | return unless class_name
|
---|
587 |
|
---|
588 | class_obj = find_class(var_name, class_name)
|
---|
589 |
|
---|
590 | if class_obj
|
---|
591 | if meth_name == "initialize"
|
---|
592 | meth_name = "new"
|
---|
593 | type = "singleton_method"
|
---|
594 | end
|
---|
595 | meth_obj = AnyMethod.new("", meth_name)
|
---|
596 | meth_obj.singleton =
|
---|
597 | %w{singleton_method module_function}.include?(type)
|
---|
598 |
|
---|
599 | p_count = (Integer(param_count) rescue -1)
|
---|
600 |
|
---|
601 | if p_count < 0
|
---|
602 | meth_obj.params = "(...)"
|
---|
603 | elsif p_count == 0
|
---|
604 | meth_obj.params = "()"
|
---|
605 | else
|
---|
606 | meth_obj.params = "(" +
|
---|
607 | (1..p_count).map{|i| "p#{i}"}.join(", ") +
|
---|
608 | ")"
|
---|
609 | end
|
---|
610 |
|
---|
611 | if source_file
|
---|
612 | file_name = File.join(@file_dir, source_file)
|
---|
613 | body = (@@known_bodies[source_file] ||= File.read(file_name))
|
---|
614 | else
|
---|
615 | body = @body
|
---|
616 | end
|
---|
617 | if find_body(meth_body, meth_obj, body) and meth_obj.document_self
|
---|
618 | class_obj.add_method(meth_obj)
|
---|
619 | end
|
---|
620 | end
|
---|
621 | end
|
---|
622 |
|
---|
623 | ############################################################
|
---|
624 |
|
---|
625 | # Find the C code corresponding to a Ruby method
|
---|
626 | def find_body(meth_name, meth_obj, body, quiet = false)
|
---|
627 | case body
|
---|
628 | when %r{((?>/\*.*?\*/\s*))(?:static\s+)?VALUE\s+#{meth_name}
|
---|
629 | \s*(\(.*?\)).*?^}xm
|
---|
630 | comment, params = $1, $2
|
---|
631 | body_text = $&
|
---|
632 |
|
---|
633 | remove_private_comments(comment) if comment
|
---|
634 |
|
---|
635 | # see if we can find the whole body
|
---|
636 |
|
---|
637 | re = Regexp.escape(body_text) + '[^(]*^\{.*?^\}'
|
---|
638 | if Regexp.new(re, Regexp::MULTILINE).match(body)
|
---|
639 | body_text = $&
|
---|
640 | end
|
---|
641 |
|
---|
642 | # The comment block may have been overridden with a
|
---|
643 | # 'Document-method' block. This happens in the interpreter
|
---|
644 | # when multiple methods are vectored through to the same
|
---|
645 | # C method but those methods are logically distinct (for
|
---|
646 | # example Kernel.hash and Kernel.object_id share the same
|
---|
647 | # implementation
|
---|
648 |
|
---|
649 | override_comment = find_override_comment(meth_obj.name)
|
---|
650 | comment = override_comment if override_comment
|
---|
651 |
|
---|
652 | find_modifiers(comment, meth_obj) if comment
|
---|
653 |
|
---|
654 | # meth_obj.params = params
|
---|
655 | meth_obj.start_collecting_tokens
|
---|
656 | meth_obj.add_token(RubyToken::Token.new(1,1).set_text(body_text))
|
---|
657 | meth_obj.comment = mangle_comment(comment)
|
---|
658 | when %r{((?>/\*.*?\*/\s*))^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
|
---|
659 | comment = $1
|
---|
660 | find_body($2, meth_obj, body, true)
|
---|
661 | find_modifiers(comment, meth_obj)
|
---|
662 | meth_obj.comment = mangle_comment(comment) + meth_obj.comment
|
---|
663 | when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
|
---|
664 | unless find_body($1, meth_obj, body, true)
|
---|
665 | warn "No definition for #{meth_name}" unless quiet
|
---|
666 | return false
|
---|
667 | end
|
---|
668 | else
|
---|
669 |
|
---|
670 | # No body, but might still have an override comment
|
---|
671 | comment = find_override_comment(meth_obj.name)
|
---|
672 |
|
---|
673 | if comment
|
---|
674 | find_modifiers(comment, meth_obj)
|
---|
675 | meth_obj.comment = mangle_comment(comment)
|
---|
676 | else
|
---|
677 | warn "No definition for #{meth_name}" unless quiet
|
---|
678 | return false
|
---|
679 | end
|
---|
680 | end
|
---|
681 | true
|
---|
682 | end
|
---|
683 |
|
---|
684 |
|
---|
685 | ##
|
---|
686 | # If the comment block contains a section that looks like:
|
---|
687 | #
|
---|
688 | # call-seq:
|
---|
689 | # Array.new
|
---|
690 | # Array.new(10)
|
---|
691 | #
|
---|
692 | # use it for the parameters.
|
---|
693 |
|
---|
694 | def find_modifiers(comment, meth_obj)
|
---|
695 | if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or
|
---|
696 | comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '')
|
---|
697 | meth_obj.document_self = false
|
---|
698 | end
|
---|
699 | if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or
|
---|
700 | comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '')
|
---|
701 | seq = $1
|
---|
702 | seq.gsub!(/^\s*\*\s*/, '')
|
---|
703 | meth_obj.call_seq = seq
|
---|
704 | end
|
---|
705 | end
|
---|
706 |
|
---|
707 | ############################################################
|
---|
708 |
|
---|
709 | def find_override_comment(meth_name)
|
---|
710 | name = Regexp.escape(meth_name)
|
---|
711 | if @body =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m
|
---|
712 | $1
|
---|
713 | end
|
---|
714 | end
|
---|
715 |
|
---|
716 | ##
|
---|
717 | # Look for includes of the form:
|
---|
718 | #
|
---|
719 | # rb_include_module(rb_cArray, rb_mEnumerable);
|
---|
720 |
|
---|
721 | def do_includes
|
---|
722 | @body.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m|
|
---|
723 | if cls = @classes[c]
|
---|
724 | m = @known_classes[m] || m
|
---|
725 | cls.add_include(Include.new(m, ""))
|
---|
726 | end
|
---|
727 | end
|
---|
728 | end
|
---|
729 |
|
---|
730 | ##
|
---|
731 | # Remove the /*'s and leading asterisks from C comments
|
---|
732 |
|
---|
733 | def mangle_comment(comment)
|
---|
734 | comment.sub!(%r{/\*+}) { " " * $&.length }
|
---|
735 | comment.sub!(%r{\*+/}) { " " * $&.length }
|
---|
736 | comment.gsub!(/^[ \t]*\*/m) { " " * $&.length }
|
---|
737 | comment
|
---|
738 | end
|
---|
739 |
|
---|
740 | def find_class(raw_name, name)
|
---|
741 | unless @classes[raw_name]
|
---|
742 | if raw_name =~ /^rb_m/
|
---|
743 | @classes[raw_name] = @top_level.add_module(NormalModule, name)
|
---|
744 | else
|
---|
745 | @classes[raw_name] = @top_level.add_class(NormalClass, name, nil)
|
---|
746 | end
|
---|
747 | end
|
---|
748 | @classes[raw_name]
|
---|
749 | end
|
---|
750 |
|
---|
751 | def handle_tab_width(body)
|
---|
752 | if /\t/ =~ body
|
---|
753 | tab_width = Options.instance.tab_width
|
---|
754 | body.split(/\n/).map do |line|
|
---|
755 | 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #`
|
---|
756 | line
|
---|
757 | end .join("\n")
|
---|
758 | else
|
---|
759 | body
|
---|
760 | end
|
---|
761 | end
|
---|
762 |
|
---|
763 | ##
|
---|
764 | # Removes #ifdefs that would otherwise confuse us
|
---|
765 |
|
---|
766 | def handle_ifdefs_in(body)
|
---|
767 | body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 }
|
---|
768 | end
|
---|
769 |
|
---|
770 | end
|
---|
771 |
|
---|
772 | end
|
---|
773 |
|
---|