PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/cmpi-bindings-0.5.2/swig/ruby/parse_swig.rb

#
Ruby | 667 lines | 551 code | 30 blank | 86 comment | 12 complexity | b31bd2b8efd88a71abf172dca9ae6290 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. # parse_swig.rb
  2. #
  3. # A rdoc parser for SWIG .i files
  4. #
  5. # Based on parse_c.rb from the Ruby-1.8.7 distribution.
  6. #
  7. # Most of (my) SWIG binding descriptions have a toplevel .i file
  8. # describing the module (%module) and generic definitions. The various
  9. # classes are spread into separate .i files, all '%include'd by the main
  10. # file.
  11. #
  12. # parse_swig.rb depends on this (a single module, with classes below it)
  13. # layout and needs the main file (the one with %module) as first input
  14. # file. A second parse of this file is ignored.
  15. #
  16. # == Usage
  17. # rdoc module.i *.i
  18. #
  19. # !! ensure that the file containing the %module directive
  20. # !! is parsed first
  21. #
  22. #
  23. # == Installation:
  24. #
  25. # Getting this file into rdoc without changing rdoc itself is a bit tricky
  26. # but doable, thanks to the dynamic nature of Ruby.
  27. #
  28. # One just needs to load parse_swig.rb before the original rdoc. The tricky
  29. # part is that 'require' in Ruby either loads an .rb or .so file, but
  30. # /usr/bin/rdoc does not have an extension.
  31. # This is solved by a creating a temporary symlink ./rdoc.rb -> /usr/bin/rdoc
  32. # and wrapping this into a new 'rdoc' to be called on the command line:
  33. #
  34. # home = File.dirname __FILE__
  35. # $:.unshift(home)
  36. # new_rdoc = home+"/rdoc.rb"
  37. # File.symlink("/usr/bin/rdoc", new_rdoc) unless File.symlink?(new_rdoc)
  38. # begin
  39. # require 'parse_swig.rb'
  40. # require 'rdoc'
  41. # ensure
  42. # File.delete new_rdoc # Discard the symlink
  43. # end
  44. #
  45. require "rdoc/code_objects"
  46. require "rdoc/parsers/parserfactory"
  47. require "rdoc/options"
  48. require "rdoc/rdoc"
  49. module RDoc
  50. class Context
  51. attr_accessor :body
  52. end
  53. class NormalClass
  54. attr_accessor :extend_name
  55. end
  56. class NormalModule
  57. attr_accessor :extend_name
  58. end
  59. class AnyMethod
  60. attr_accessor :orig_name
  61. end
  62. class Swig_Parser
  63. attr_accessor :progress
  64. extend ParserFactory
  65. parse_files_matching(/\.i$/)
  66. @@known_bodies = {}
  67. @@files_seen = Array.new
  68. @@module_name = nil
  69. # prepare to parse a SWIG file
  70. # RHEL4 has Ruby 1.8.1 which does not provide stats
  71. def initialize(top_level, file_name, body, options, stats = nil)
  72. @known_classes = KNOWN_CLASSES.dup
  73. @body = handle_tab_width(handle_ifdefs_in(body))
  74. @options = options
  75. @stats = stats
  76. @top_level = top_level
  77. @classes = Hash.new
  78. @file_dir = File.dirname(file_name)
  79. @progress = $stderr unless options.quiet
  80. @file_name = file_name
  81. end
  82. # Extract the classes/modules and methods from a C file
  83. # and return the corresponding top-level object
  84. def scan
  85. unless @@files_seen.include? @file_name
  86. @@files_seen << @file_name
  87. remove_commented_out_lines
  88. if module_name = do_module
  89. @@module_name = module_name
  90. do_methods module_name
  91. else
  92. do_classes
  93. @classes.keys.each do |c|
  94. do_constants c
  95. do_methods c
  96. do_includes
  97. do_aliases c
  98. end
  99. end
  100. else
  101. puts "Seen #{@file_name} before" unless @options.quiet
  102. end
  103. @top_level
  104. end
  105. #######
  106. private
  107. #######
  108. def progress(char)
  109. unless @options.quiet
  110. @progress.print(char)
  111. @progress.flush
  112. end
  113. end
  114. def warn(msg)
  115. $stderr.puts
  116. $stderr.puts msg
  117. $stderr.flush
  118. end
  119. def remove_private_comments(comment)
  120. comment.gsub!(/\/?\*--(.*?)\/?\*\+\+/m, '')
  121. comment.sub!(/\/?\*--.*/m, '')
  122. end
  123. ##
  124. # removes lines that are commented out that might otherwise get picked up
  125. # when scanning for classes and methods
  126. def remove_commented_out_lines
  127. @body.gsub!(%r{//.*rb_define_}, '//')
  128. end
  129. ##
  130. # handle class or module
  131. #
  132. # return enclosure
  133. #
  134. def handle_class_module(class_mod, class_name, options = {})
  135. # puts "handle_class_module(#{class_mod}, #{class_name})"
  136. progress(class_mod[0, 1])
  137. parent = options[:parent]
  138. parent_name = @known_classes[parent] || parent
  139. if @@module_name
  140. enclosure = @top_level.find_module_named(@@module_name)
  141. else
  142. enclosure = @top_level
  143. end
  144. if RUBY_VERSION == "1.8.1"
  145. return nil unless enclosure # workaround for RHEL4
  146. end
  147. if class_mod == "class"
  148. cm = enclosure.add_class(NormalClass, class_name, parent_name)
  149. @stats.num_classes += 1 if @stats
  150. else
  151. cm = enclosure.add_module(NormalModule, class_name)
  152. @stats.num_modules += 1 if @stats
  153. end
  154. cm.record_location(enclosure.toplevel)
  155. cm.body = options[:content]
  156. cm.extend_name = options[:extend_name] || class_name
  157. find_class_comment(class_name, cm)
  158. @classes[class_name] = cm
  159. @known_classes[class_name] = cm.full_name
  160. end
  161. ##
  162. # Look for class or module documentation above %extend +class_name+
  163. # in a Document-class +class_name+ (or module) comment or above an
  164. # rb_define_class (or module). If a comment is supplied above a matching
  165. # Init_ and a rb_define_class the Init_ comment is used.
  166. #
  167. # /*
  168. # * This is a comment for Foo
  169. # */
  170. # %extend Foo {
  171. # ...
  172. # }
  173. #
  174. def find_class_comment(class_name, class_meth)
  175. # puts "find_class_comment(#{class_name}, #{class_meth.extend_name})"
  176. comment = nil
  177. if @body =~ %r{((?>/\*.*?\*/\s+))
  178. %extend\s+#{class_meth.extend_name}\s*\{}xm
  179. comment = $1
  180. elsif @body =~ %r{Document-(class|module):\s#{class_name}\s*?\n((?>.*?\*/))}m
  181. comment = $2
  182. end
  183. class_meth.comment = mangle_comment(comment) if comment
  184. end
  185. ############################################################
  186. #
  187. # Find module
  188. # return module_name (or nil)
  189. #
  190. def do_module
  191. module_name = nil
  192. @body.scan(/^%module\s*(\w+)/mx) do
  193. |name|
  194. module_name = name.to_s
  195. module_name.capitalize! unless module_name[0,1] =~ /[A-Z_]/
  196. handle_class_module("module", module_name)
  197. break
  198. end
  199. module_name
  200. end
  201. #
  202. # Find and handle classes within +module_name+
  203. #
  204. # return Array of classes
  205. #
  206. def do_classes
  207. # look for class renames like
  208. # %rename(Solvable) _Solvable;
  209. # typedef struct _Solvable {} XSolvable; /* expose XSolvable as 'Solvable' */
  210. extends = Hash.new
  211. @body.scan(/^%rename\s*\(([^\"\)]+)\)\s+([_\w]+);/) do |class_name, struct_name|
  212. # puts "rename #{class_name} -> #{struct_name}"
  213. extend_name = struct_name.to_s
  214. @body.scan(/typedef\s+struct\s+#{struct_name}\s*\{[^}]*\}\s*(\w+);/) do |ename|
  215. extend_name = ename.to_s
  216. end
  217. # puts "extend #{extend_name}, class #{class_name}, struct #{struct_name}"
  218. # find the corresponding '%extend' directive
  219. @body.scan(/^%extend\s+(#{extend_name}|#{struct_name})\s*\{(.*)\}/mx) do |name,content|
  220. # now check if we have multiple %extend, the regexp above is greedy and will match all of them
  221. while content.to_s =~ /^%extend/
  222. content = $` # discard %extend and everything behind
  223. end
  224. extends[name] = true
  225. cn = class_name.to_s
  226. cn.capitalize! unless cn[0,1] =~ /[A-Z_]/
  227. handle_class_module("class", cn, :parent => "rb_cObject", :content => content.to_s, :extend_name => name)
  228. end
  229. end
  230. @body.scan(/^%extend\s*(\w+)\s*\{(.*)\}/mx) do |class_name,content|
  231. cn = class_name.to_s
  232. unless extends[cn]
  233. # puts "Class #{cn}"
  234. cn.capitalize! unless cn[0,1] =~ /[A-Z_]/
  235. handle_class_module("class", cn, :parent => "rb_cObject", :content => content)
  236. extends[cn] = true
  237. end
  238. end
  239. end
  240. ###########################################################
  241. #
  242. # Find
  243. # %constant +type+ +name+ = +value+
  244. #
  245. def do_constants class_name
  246. c = find_class class_name
  247. c.body.scan(%r{%constant\s+(\w+)\s+(\w+)\s*=\s*(\w+)\s*;}xm) do
  248. |type, const_name, definition|
  249. # swig puts all constants under module
  250. handle_constants(type, @@module_name, const_name, definition)
  251. end
  252. end
  253. ############################################################
  254. #
  255. # Find and handle all methods for +module_name+::+class_name+
  256. #
  257. # Look for C-Function headers within the class content
  258. # const? +type+ +name+ ( +args+ ) {
  259. # and honor
  260. # %rename "+new_name+" +old_name+ ;
  261. #
  262. # Module level methods have 'static' prototypes of C functions.
  263. # e.g. static type name(args);
  264. #
  265. def do_methods class_name
  266. renames = Hash.new
  267. c = find_class class_name
  268. body = c.body || @body
  269. extend_name = c.extend_name
  270. body.scan(%r{%rename\s*\(\s*"([^"]+)"\s*\)\s*(\w+)}m) do #"
  271. |meth_name,orig_name|
  272. meth_name = meth_name.to_s
  273. orig_name = orig_name.to_s
  274. renames[orig_name] = meth_name
  275. end
  276. # Find function definitions of the format
  277. # <type> [*]? <name> ( <args> ) {
  278. #
  279. # puts "#{module_name}::#{class_name} methods ?"
  280. # Find class constructor as 'new'
  281. body.scan(%r{^\s+#{extend_name}\s*\(([^\)]*)\)\s*\{}m) do
  282. |args|
  283. handle_method(class_name, class_name, "initialize", nil, (args.to_s.split(",")||[]).size, nil)
  284. end if extend_name
  285. body.scan(%r{static\s+((const\s+)?\w+)([\s\*]+)(\w+)\s*\(([^\)]*)\)\s*;}) do
  286. |const,type,pointer,meth_name,args|
  287. # puts "-> const #{const}:type #{type}:pointer #{pointer}:name #{meth_name} (args #{args} )\n#{$&}\n\n"
  288. meth_name = orig_name = meth_name.to_s
  289. meth_name = renames[meth_name] || meth_name
  290. handle_method(type, class_name||@@module_name, meth_name, nil, (args.split(",")||[]).size, orig_name)
  291. end
  292. body.scan(%r{((const\s+)?\w+)([ \t\*]+)(\w+)\s*\(([^\)]*)\)\s*\{}m) do
  293. |const,type,pointer,meth_name,args|
  294. next unless meth_name
  295. next if meth_name =~ /~/
  296. type = "string" if type =~ /char/ && pointer =~ /\*/
  297. # puts "-> const #{const}:type #{type}:pointer #{pointer}:name #{meth_name} (args #{args} )\n#{$&}\n\n" if meth_name == "if"
  298. meth_name = orig_name = meth_name.to_s
  299. meth_name = renames[meth_name] || meth_name
  300. handle_method(type, class_name, meth_name, nil, (args.split(",")||[]).size, orig_name)
  301. end
  302. end
  303. ############################################################
  304. #
  305. # Find and handle method aliases
  306. #
  307. # %alias +old_name+ "+new_name+" ;
  308. #
  309. def do_aliases class_name
  310. c = find_class class_name
  311. c.body.scan(%r{%alias\s+(\w+)\s+"([^"]+)"\s*;}m) do #"
  312. |old_name, new_name|
  313. @stats.num_methods += 1 if @stats
  314. raise "Unknown class '#{class_name}'" unless @known_classes[class_name]
  315. class_obj = find_class(class_name)
  316. class_obj.add_alias(Alias.new("", old_name, new_name, ""))
  317. end
  318. end
  319. ##
  320. # Adds constant comments. By providing some_value: at the start ofthe
  321. # comment you can override the C value of the comment to give a friendly
  322. # definition.
  323. #
  324. # /* 300: The perfect score in bowling */
  325. # rb_define_const(cFoo, "PERFECT", INT2FIX(300);
  326. #
  327. # Will override +INT2FIX(300)+ with the value +300+ in the output RDoc.
  328. # Values may include quotes and escaped colons (\:).
  329. def handle_constants(type, class_name, const_name, definition)
  330. class_obj = find_class(class_name)
  331. unless class_obj
  332. warn("Enclosing class/module for '#{const_name}' not known")
  333. return
  334. end
  335. comment = find_const_comment(type, const_name)
  336. # In the case of rb_define_const, the definition and comment are in
  337. # "/* definition: comment */" form. The literal ':' and '\' characters
  338. # can be escaped with a backslash.
  339. if type.downcase == 'const' then
  340. elements = mangle_comment(comment).split(':')
  341. if elements.nil? or elements.empty? then
  342. con = Constant.new(const_name, definition, mangle_comment(comment))
  343. else
  344. new_definition = elements[0..-2].join(':')
  345. if new_definition.empty? then # Default to literal C definition
  346. new_definition = definition
  347. else
  348. new_definition.gsub!("\:", ":")
  349. new_definition.gsub!("\\", '\\')
  350. end
  351. new_definition.sub!(/\A(\s+)/, '')
  352. new_comment = $1.nil? ? elements.last : "#{$1}#{elements.last.lstrip}"
  353. con = Constant.new(const_name, new_definition,
  354. mangle_comment(new_comment))
  355. end
  356. else
  357. con = Constant.new(const_name, definition, mangle_comment(comment))
  358. end
  359. class_obj.add_constant(con)
  360. end
  361. ##
  362. # Finds a comment matching +type+ and +const_name+ either above the
  363. # comment or in the matching Document- section.
  364. def find_const_comment(type, const_name)
  365. if @body =~ %r{((?>^\s*/\*.*?\*/\s+))
  366. %constant\s+(\w+)\s+#{const_name}\s*=\s*(\w+)\s*;}xmi
  367. $1
  368. elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m
  369. $1
  370. else
  371. ''
  372. end
  373. end
  374. ###########################################################
  375. def handle_attr(var_name, attr_name, reader, writer)
  376. rw = ''
  377. if reader
  378. #@stats.num_methods += 1
  379. rw << 'R'
  380. end
  381. if writer
  382. #@stats.num_methods += 1
  383. rw << 'W'
  384. end
  385. class_name = @known_classes[var_name]
  386. return unless class_name
  387. class_obj = find_class(class_name)
  388. if class_obj
  389. comment = find_attr_comment(attr_name)
  390. unless comment.empty?
  391. comment = mangle_comment(comment)
  392. end
  393. att = Attr.new('', attr_name, rw, comment)
  394. class_obj.add_attribute(att)
  395. end
  396. end
  397. ###########################################################
  398. def find_attr_comment(attr_name)
  399. if @body =~ %r{((?>/\*.*?\*/\s+))
  400. rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi
  401. $1
  402. elsif @body =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m
  403. $1
  404. else
  405. ''
  406. end
  407. end
  408. ###########################################################
  409. def handle_method(type, class_name, meth_name,
  410. meth_body, param_count, orig_name)
  411. progress(".")
  412. class_obj = find_class(class_name)
  413. return nil unless class_obj
  414. seen_before = class_obj.method_list.find { |meth| meth.name == meth_name }
  415. return nil if seen_before
  416. @stats.num_methods += 1 if @stats
  417. if meth_name == "initialize"
  418. meth_name = "new"
  419. type = "singleton_method"
  420. end
  421. meth_obj = AnyMethod.new("", meth_name)
  422. meth_obj.singleton = %w{singleton_method module_function}.include?(type)
  423. meth_obj.orig_name = orig_name || meth_name
  424. p_count = (Integer(param_count) rescue -1)
  425. if p_count < 0
  426. meth_obj.params = "(...)"
  427. elsif p_count == 0
  428. meth_obj.params = "()"
  429. else
  430. meth_obj.params = "(" +
  431. (1..p_count).map{|i| "p#{i}"}.join(", ") +
  432. ")"
  433. end
  434. body = find_class(class_name).body || @body
  435. if find_body(class_name, meth_name, meth_obj, body) and meth_obj.document_self
  436. class_obj.add_method(meth_obj)
  437. end
  438. meth_obj
  439. end
  440. ############################################################
  441. # Find the C code corresponding to a Ruby method
  442. def find_body(class_name, meth_name, meth_obj, body, quiet = false)
  443. case body
  444. when %r{((?>/\*.*?\*/\s*))(?:static\s+)?(?:const\s+)?(\w+)[\s\*]+#{meth_obj.orig_name || meth_name}
  445. \s*(\(.*?\)).*?^}xm
  446. comment, params = $1, $2
  447. body_text = $&
  448. remove_private_comments(comment) if comment
  449. # see if we can find the whole body
  450. re = Regexp.escape(body_text) + '[^(]*^\{.*?^\}'
  451. if Regexp.new(re, Regexp::MULTILINE).match(body)
  452. body_text = $&
  453. end
  454. # The comment block may have been overridden with a
  455. # 'Document-method' block. This happens in the interpreter
  456. # when multiple methods are vectored through to the same
  457. # C method but those methods are logically distinct (for
  458. # example Kernel.hash and Kernel.object_id share the same
  459. # implementation
  460. override_comment = find_override_comment(meth_obj.name)
  461. comment = override_comment if override_comment
  462. find_modifiers(comment, meth_obj) if comment
  463. # meth_obj.params = params
  464. meth_obj.start_collecting_tokens
  465. meth_obj.add_token(RubyToken::Token.new(1,1).set_text(body_text))
  466. meth_obj.comment = mangle_comment(comment)
  467. when %r{((?>/\*.*?\*/\s*))^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
  468. comment = $1
  469. find_body(class_name, $2, meth_obj, body, true)
  470. find_modifiers(comment, meth_obj)
  471. meth_obj.comment = mangle_comment(comment) + meth_obj.comment
  472. when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
  473. unless find_body(class_name, $1, meth_obj, body, true)
  474. warn "No definition for #{meth_name}" unless quiet
  475. return false
  476. end
  477. else
  478. if (meth_name == "new")
  479. # find constructor definition
  480. extend_name = find_class(class_name).extend_name
  481. if body =~ %r{((?>/\*.*?\*/\s*))\s*#{extend_name}\s*(\([^)]*\))}xm
  482. comment = $1
  483. find_modifiers(comment, meth_obj)
  484. meth_obj.comment = mangle_comment(comment) + meth_obj.comment
  485. # No body, but might still have an override comment
  486. comment = find_override_comment(meth_obj.name)
  487. end
  488. end
  489. if comment
  490. find_modifiers(comment, meth_obj)
  491. meth_obj.comment = mangle_comment(comment)
  492. else
  493. # warn "Dummy definition for #{meth_name}" unless quiet
  494. # find_modifiers("unknown", meth_obj)
  495. # meth_obj.comment = mangle_comment("unknown") + meth_obj.comment
  496. end
  497. end
  498. true
  499. end
  500. ##
  501. # If the comment block contains a section that looks like:
  502. #
  503. # call-seq:
  504. # Array.new
  505. # Array.new(10)
  506. #
  507. # use it for the parameters.
  508. def find_modifiers(comment, meth_obj)
  509. if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or
  510. comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '')
  511. meth_obj.document_self = false
  512. end
  513. if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or
  514. comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '')
  515. seq = $1
  516. seq.gsub!(/^\s*\*\s*/, '')
  517. meth_obj.call_seq = seq
  518. end
  519. end
  520. ############################################################
  521. def find_override_comment(meth_name)
  522. name = Regexp.escape(meth_name)
  523. if @body =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m
  524. $1
  525. end
  526. end
  527. ##
  528. # Look for includes of the form:
  529. #
  530. # %mixin class "module";
  531. def do_includes
  532. @body.scan(/%mixin\s+(\w+)\s+"([^"]+)"s*;/) do |c,m| #"
  533. if cls = @classes[c]
  534. m = @known_classes[m] || m
  535. cls.add_include(Include.new(m, ""))
  536. end
  537. end
  538. end
  539. ##
  540. # Remove the /*'s and leading asterisks from C comments
  541. def mangle_comment(comment)
  542. comment.sub!(%r{/\*+}) { " " * $&.length }
  543. comment.sub!(%r{\*+/}) { " " * $&.length }
  544. comment.gsub!(/^[ \t]*\*/m) { " " * $&.length }
  545. comment
  546. end
  547. def find_class(name)
  548. @classes[name] || @top_level.find_module_named(name) || raise("No such class '#{name}'")
  549. end
  550. def handle_tab_width(body)
  551. if /\t/ =~ body
  552. tab_width = Options.instance.tab_width
  553. body.split(/\n/).map do |line|
  554. 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #`
  555. line
  556. end .join("\n")
  557. else
  558. body
  559. end
  560. end
  561. ##
  562. # Removes #ifdefs that would otherwise confuse us
  563. def handle_ifdefs_in(body)
  564. # remove all %{...%}
  565. while body =~ /^%\{/
  566. before = $` # keep whats before %{
  567. $' =~ /^%\}/ # scan the rest for %} '
  568. body = before + $' # keep whats after %} '
  569. end
  570. body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 }
  571. end
  572. end
  573. end