PageRenderTime 199ms CodeModel.GetById 149ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 1ms

/tools/Ruby/lib/ruby/1.8/rdoc/code_objects.rb

http://github.com/agross/netopenspace
Ruby | 765 lines | 587 code | 110 blank | 68 comment | 37 complexity | 1622d96f389850dd43d6928f223f2960 MD5 | raw file
  1# We represent the various high-level code constructs that appear
  2# in Ruby programs: classes, modules, methods, and so on.
  3
  4require 'rdoc/tokenstream'
  5
  6module RDoc
  7
  8
  9  # We contain the common stuff for contexts (which are containers)
 10  # and other elements (methods, attributes and so on)
 11  #
 12  class CodeObject
 13
 14    attr_accessor :parent
 15
 16    # We are the model of the code, but we know that at some point
 17    # we will be worked on by viewers. By implementing the Viewable
 18    # protocol, viewers can associated themselves with these objects.
 19
 20    attr_accessor :viewer
 21
 22    # are we done documenting (ie, did we come across a :enddoc:)?
 23
 24    attr_accessor :done_documenting
 25
 26    # Which section are we in
 27
 28    attr_accessor :section
 29
 30    # do we document ourselves?
 31
 32    attr_reader :document_self
 33
 34    def document_self=(val)
 35      @document_self = val
 36      if !val
 37	remove_methods_etc
 38      end
 39    end
 40
 41    # set and cleared by :startdoc: and :enddoc:, this is used to toggle
 42    # the capturing of documentation
 43    def start_doc
 44      @document_self = true
 45      @document_children = true
 46    end
 47
 48    def stop_doc
 49      @document_self = false
 50      @document_children = false
 51    end
 52
 53    # do we document ourselves and our children
 54
 55    attr_reader :document_children
 56
 57    def document_children=(val)
 58      @document_children = val
 59      if !val
 60	remove_classes_and_modules
 61      end
 62    end
 63
 64    # Do we _force_ documentation, even is we wouldn't normally show the entity
 65    attr_accessor :force_documentation
 66
 67    # Default callbacks to nothing, but this is overridden for classes
 68    # and modules
 69    def remove_classes_and_modules
 70    end
 71
 72    def remove_methods_etc
 73    end
 74
 75    def initialize
 76      @document_self = true
 77      @document_children = true
 78      @force_documentation = false
 79      @done_documenting = false
 80    end
 81
 82    # Access the code object's comment
 83    attr_reader :comment
 84
 85    # Update the comment, but don't overwrite a real comment
 86    # with an empty one
 87    def comment=(comment)
 88      @comment = comment unless comment.empty?
 89    end
 90
 91    # There's a wee trick we pull. Comment blocks can have directives that
 92    # override the stuff we extract during the parse. So, we have a special
 93    # class method, attr_overridable, that lets code objects list
 94    # those directives. Wehn a comment is assigned, we then extract
 95    # out any matching directives and update our object
 96
 97    def CodeObject.attr_overridable(name, *aliases)
 98      @overridables ||= {}
 99
100      attr_accessor name
101
102      aliases.unshift name
103      aliases.each do |directive_name|
104        @overridables[directive_name.to_s] = name
105      end
106    end
107
108  end
109
110  # A Context is something that can hold modules, classes, methods, 
111  # attributes, aliases, requires, and includes. Classes, modules, and
112  # files are all Contexts.
113
114  class Context < CodeObject
115    attr_reader   :name, :method_list, :attributes, :aliases, :constants
116    attr_reader   :requires, :includes, :in_files, :visibility
117
118    attr_reader   :sections
119
120    class Section
121      attr_reader :title, :comment, :sequence
122
123      @@sequence = "SEC00000"
124
125      def initialize(title, comment)
126        @title = title
127        @@sequence.succ!
128        @sequence = @@sequence.dup
129        set_comment(comment)
130      end
131
132      private
133
134      # Set the comment for this section from the original comment block
135      # If the first line contains :section:, strip it and use the rest. Otherwise
136      # remove lines up to the line containing :section:, and look for 
137      # those lines again at the end and remove them. This lets us write
138      #
139      #   # ---------------------
140      #   # :SECTION: The title
141      #   # The body
142      #   # ---------------------
143
144      def set_comment(comment)
145        return unless comment
146
147        if comment =~ /^.*?:section:.*$/
148          start = $`
149          rest = $'
150          if start.empty?
151            @comment = rest
152          else
153            @comment = rest.sub(/#{start.chomp}\Z/, '')
154          end
155        else
156          @comment = comment
157        end
158        @comment = nil if @comment.empty?
159      end
160    end
161
162
163    def initialize
164      super()
165
166      @in_files    = []
167
168      @name    ||= "unknown"
169      @comment ||= ""
170      @parent  = nil
171      @visibility = :public
172
173      @current_section = Section.new(nil, nil)
174      @sections = [ @current_section ]
175
176      initialize_methods_etc
177      initialize_classes_and_modules
178    end
179
180    # map the class hash to an array externally
181    def classes
182      @classes.values
183    end
184
185    # map the module hash to an array externally
186    def modules
187      @modules.values
188    end
189
190    # Change the default visibility for new methods
191    def ongoing_visibility=(vis)
192      @visibility = vis
193    end
194
195    # Given an array +methods+ of method names, set the
196    # visibility of the corresponding AnyMethod object
197
198    def set_visibility_for(methods, vis, singleton=false)
199      count = 0
200      @method_list.each do |m|
201        if methods.include?(m.name) && m.singleton == singleton
202          m.visibility = vis
203          count += 1
204        end
205      end
206
207      return if count == methods.size || singleton
208
209      # perhaps we need to look at attributes
210
211      @attributes.each do |a|
212        if methods.include?(a.name)
213          a.visibility = vis
214          count += 1
215        end
216      end
217    end
218
219    # Record the file that we happen to find it in
220    def record_location(toplevel)
221      @in_files << toplevel unless @in_files.include?(toplevel)
222    end
223
224    # Return true if at least part of this thing was defined in +file+
225    def defined_in?(file)
226      @in_files.include?(file)
227    end
228
229    def add_class(class_type, name, superclass)
230      add_class_or_module(@classes, class_type, name, superclass)
231    end
232
233    def add_module(class_type, name)
234      add_class_or_module(@modules, class_type, name, nil)
235    end
236
237    def add_method(a_method)
238      puts "Adding #@visibility method #{a_method.name} to #@name" if $DEBUG
239      a_method.visibility = @visibility
240      add_to(@method_list, a_method)
241    end
242
243    def add_attribute(an_attribute)
244      add_to(@attributes, an_attribute)
245    end
246
247    def add_alias(an_alias)
248      meth = find_instance_method_named(an_alias.old_name)
249      if meth
250        new_meth = AnyMethod.new(an_alias.text, an_alias.new_name)
251        new_meth.is_alias_for = meth
252        new_meth.singleton    = meth.singleton
253        new_meth.params       = meth.params
254        new_meth.comment = "Alias for \##{meth.name}"
255        meth.add_alias(new_meth)
256        add_method(new_meth)
257      else
258        add_to(@aliases, an_alias)
259      end
260    end
261
262    def add_include(an_include)
263      add_to(@includes, an_include)
264    end
265
266    def add_constant(const)
267      add_to(@constants, const)
268    end
269
270    # Requires always get added to the top-level (file) context
271    def add_require(a_require)
272      if self.kind_of? TopLevel
273        add_to(@requires, a_require)
274      else
275        parent.add_require(a_require)
276      end
277    end
278
279    def add_class_or_module(collection, class_type, name, superclass=nil)
280      cls = collection[name]
281      if cls
282        puts "Reusing class/module #{name}" if $DEBUG
283      else
284        cls = class_type.new(name, superclass)
285        puts "Adding class/module #{name} to #@name" if $DEBUG
286#        collection[name] = cls if @document_self  && !@done_documenting
287        collection[name] = cls if !@done_documenting
288        cls.parent = self
289        cls.section = @current_section
290      end
291      cls
292    end
293
294    def add_to(array, thing)
295      array <<  thing if @document_self  && !@done_documenting
296      thing.parent = self
297      thing.section = @current_section
298    end
299
300    # If a class's documentation is turned off after we've started
301    # collecting methods etc., we need to remove the ones
302    # we have
303
304    def remove_methods_etc
305      initialize_methods_etc
306    end
307
308    def initialize_methods_etc
309      @method_list = []
310      @attributes  = []
311      @aliases     = []
312      @requires    = []
313      @includes    = []
314      @constants   = []
315    end
316
317    # and remove classes and modules when we see a :nodoc: all
318    def remove_classes_and_modules
319      initialize_classes_and_modules
320    end
321
322    def initialize_classes_and_modules
323      @classes     = {}
324      @modules     = {}
325    end
326
327    # Find a named module
328    def find_module_named(name)
329      return self if self.name == name
330      res = @modules[name] || @classes[name]
331      return res if res
332      find_enclosing_module_named(name)
333    end
334
335    # find a module at a higher scope
336    def find_enclosing_module_named(name)
337      parent && parent.find_module_named(name)
338    end
339
340    # Iterate over all the classes and modules in
341    # this object
342
343    def each_classmodule
344      @modules.each_value {|m| yield m}
345      @classes.each_value {|c| yield c}
346    end
347
348    def each_method
349      @method_list.each {|m| yield m}
350    end
351
352    def each_attribute 
353      @attributes.each {|a| yield a}
354    end
355
356    def each_constant
357      @constants.each {|c| yield c}
358    end
359
360    # Return the toplevel that owns us
361
362    def toplevel
363      return @toplevel if defined? @toplevel
364      @toplevel = self
365      @toplevel = @toplevel.parent until TopLevel === @toplevel
366      @toplevel
367    end
368
369    # allow us to sort modules by name
370    def <=>(other)
371      name <=> other.name
372    end
373
374    # Look up the given symbol. If method is non-nil, then
375    # we assume the symbol references a module that
376    # contains that method
377    def find_symbol(symbol, method=nil)
378      result = nil
379      case symbol
380      when /^::(.*)/
381        result = toplevel.find_symbol($1)
382      when /::/
383        modules = symbol.split(/::/)
384        unless modules.empty?
385          module_name = modules.shift
386          result = find_module_named(module_name)
387          if result
388            modules.each do |module_name|
389              result = result.find_module_named(module_name)
390              break unless result
391            end
392          end
393        end
394      else
395        # if a method is specified, then we're definitely looking for
396        # a module, otherwise it could be any symbol
397        if method
398          result = find_module_named(symbol)
399        else
400          result = find_local_symbol(symbol)
401          if result.nil?
402            if symbol =~ /^[A-Z]/
403              result = parent
404              while result && result.name != symbol
405                result = result.parent
406              end
407            end
408          end
409        end
410      end
411      if result && method
412        if !result.respond_to?(:find_local_symbol)
413          p result.name
414          p method
415          fail
416        end
417        result = result.find_local_symbol(method)
418      end
419      result
420    end
421           
422    def find_local_symbol(symbol)
423      res = find_method_named(symbol) ||
424            find_constant_named(symbol) ||
425            find_attribute_named(symbol) ||
426            find_module_named(symbol) 
427    end
428
429    # Handle sections
430
431    def set_current_section(title, comment)
432      @current_section = Section.new(title, comment)
433      @sections << @current_section
434    end
435
436    private
437
438    # Find a named method, or return nil
439    def find_method_named(name)
440      @method_list.find {|meth| meth.name == name}
441    end
442
443    # Find a named instance method, or return nil
444    def find_instance_method_named(name)
445      @method_list.find {|meth| meth.name == name && !meth.singleton}
446    end
447
448    # Find a named constant, or return nil
449    def find_constant_named(name)
450      @constants.find {|m| m.name == name}
451    end
452
453    # Find a named attribute, or return nil
454    def find_attribute_named(name)
455      @attributes.find {|m| m.name == name}
456    end
457    
458  end
459
460
461  # A TopLevel context is a source file
462
463  class TopLevel < Context
464    attr_accessor :file_stat
465    attr_accessor :file_relative_name
466    attr_accessor :file_absolute_name
467    attr_accessor :diagram
468    
469    @@all_classes = {}
470    @@all_modules = {}
471
472    def TopLevel::reset
473      @@all_classes = {}
474      @@all_modules = {}
475    end
476
477    def initialize(file_name)
478      super()
479      @name = "TopLevel"
480      @file_relative_name = file_name
481      @file_absolute_name = file_name
482      @file_stat          = File.stat(file_name)
483      @diagram            = nil
484    end
485
486    def full_name
487      nil
488    end
489
490    # Adding a class or module to a TopLevel is special, as we only
491    # want one copy of a particular top-level class. For example,
492    # if both file A and file B implement class C, we only want one
493    # ClassModule object for C. This code arranges to share
494    # classes and modules between files.
495
496    def add_class_or_module(collection, class_type, name, superclass)
497      cls = collection[name]
498      if cls
499        puts "Reusing class/module #{name}" if $DEBUG
500      else
501        if class_type == NormalModule
502          all = @@all_modules
503        else
504          all = @@all_classes
505        end
506        cls = all[name]
507        if !cls
508          cls = class_type.new(name, superclass)
509          all[name] = cls  unless @done_documenting
510        end
511        puts "Adding class/module #{name} to #@name" if $DEBUG
512        collection[name] = cls unless @done_documenting
513        cls.parent = self
514      end
515      cls
516    end
517
518    def TopLevel.all_classes_and_modules
519      @@all_classes.values + @@all_modules.values
520    end
521
522    def TopLevel.find_class_named(name)
523     @@all_classes.each_value do |c|
524        res = c.find_class_named(name) 
525        return res if res
526      end
527      nil
528    end
529
530    def find_local_symbol(symbol)
531      find_class_or_module_named(symbol) || super
532    end
533
534    def find_class_or_module_named(symbol)
535      @@all_classes.each_value {|c| return c if c.name == symbol}
536      @@all_modules.each_value {|m| return m if m.name == symbol}
537      nil
538    end
539
540    # Find a named module
541    def find_module_named(name)
542      find_class_or_module_named(name) || find_enclosing_module_named(name)
543    end
544
545
546  end
547
548  # ClassModule is the base class for objects representing either a
549  # class or a module.
550
551  class ClassModule < Context
552
553    attr_reader   :superclass
554    attr_accessor :diagram
555
556    def initialize(name, superclass = nil)
557      @name       = name
558      @diagram    = nil
559      @superclass = superclass
560      @comment    = ""
561      super()
562    end
563
564    # Return the fully qualified name of this class or module
565    def full_name
566      if @parent && @parent.full_name
567        @parent.full_name + "::" + @name
568      else
569        @name
570      end
571    end
572
573    def http_url(prefix)
574      path = full_name.split("::")
575      File.join(prefix, *path) + ".html"
576    end
577
578    # Return +true+ if this object represents a module
579    def is_module?
580      false
581    end
582
583    # to_s is simply for debugging
584    def to_s
585      res = self.class.name + ": " + @name 
586      res << @comment.to_s
587      res << super
588      res
589    end
590
591    def find_class_named(name)
592      return self if full_name == name
593      @classes.each_value {|c| return c if c.find_class_named(name) }
594      nil
595    end
596  end
597
598  # Anonymous classes
599  class AnonClass < ClassModule
600  end
601
602  # Normal classes
603  class NormalClass < ClassModule
604  end
605
606  # Singleton classes
607  class SingleClass < ClassModule
608  end
609
610  # Module
611  class NormalModule < ClassModule
612    def is_module?
613      true
614    end
615  end
616
617
618  # AnyMethod is the base class for objects representing methods
619
620  class AnyMethod < CodeObject
621    attr_accessor :name
622    attr_accessor :visibility
623    attr_accessor :block_params
624    attr_accessor :dont_rename_initialize
625    attr_accessor :singleton
626    attr_reader   :aliases           # list of other names for this method
627    attr_accessor :is_alias_for      # or a method we're aliasing
628
629    attr_overridable :params, :param, :parameters, :parameter
630
631    attr_accessor :call_seq
632
633
634    include TokenStream
635
636    def initialize(text, name)
637      super()
638      @text = text
639      @name = name
640      @token_stream  = nil
641      @visibility    = :public
642      @dont_rename_initialize = false
643      @block_params  = nil
644      @aliases       = []
645      @is_alias_for  = nil
646      @comment = ""
647      @call_seq = nil
648    end
649
650    def <=>(other)
651      @name <=> other.name
652    end
653
654    def to_s
655      res = self.class.name + ": " + @name + " (" + @text + ")\n"
656      res << @comment.to_s
657      res
658    end
659
660    def param_seq
661      p = params.gsub(/\s*\#.*/, '')
662      p = p.tr("\n", " ").squeeze(" ")
663      p = "(" + p + ")" unless p[0] == ?(
664
665      if (block = block_params)
666        # If this method has explicit block parameters, remove any
667        # explicit &block
668$stderr.puts p
669        p.sub!(/,?\s*&\w+/)
670$stderr.puts p
671
672        block.gsub!(/\s*\#.*/, '')
673        block = block.tr("\n", " ").squeeze(" ")
674        if block[0] == ?(
675          block.sub!(/^\(/, '').sub!(/\)/, '')
676        end
677        p << " {|#{block}| ...}"
678      end
679      p
680    end
681
682    def add_alias(method)
683      @aliases << method
684    end
685  end
686
687
688  # Represent an alias, which is an old_name/ new_name pair associated
689  # with a particular context
690  class Alias < CodeObject
691    attr_accessor :text, :old_name, :new_name, :comment
692    
693    def initialize(text, old_name, new_name, comment)
694      super()
695      @text = text
696      @old_name = old_name
697      @new_name = new_name
698      self.comment = comment
699    end
700
701    def to_s
702      "alias: #{self.old_name} ->  #{self.new_name}\n#{self.comment}"
703    end
704  end
705
706  # Represent a constant
707  class Constant < CodeObject
708    attr_accessor :name, :value
709
710    def initialize(name, value, comment)
711      super()
712      @name = name
713      @value = value
714      self.comment = comment
715    end
716  end
717
718  # Represent attributes
719  class Attr < CodeObject
720    attr_accessor :text, :name, :rw, :visibility
721
722    def initialize(text, name, rw, comment)
723      super()
724      @text = text
725      @name = name
726      @rw = rw
727      @visibility = :public
728      self.comment = comment
729    end
730
731    def to_s
732      "attr: #{self.name} #{self.rw}\n#{self.comment}"
733    end
734
735    def <=>(other)
736      self.name <=> other.name
737    end
738  end
739
740  # a required file
741
742  class Require < CodeObject
743    attr_accessor :name
744
745    def initialize(name, comment)
746      super()
747      @name = name.gsub(/'|"/, "") #'
748      self.comment = comment
749    end
750
751  end
752
753  # an included module
754  class Include < CodeObject
755    attr_accessor :name
756
757    def initialize(name, comment)
758      super()
759      @name = name
760      self.comment = comment
761    end
762
763  end
764
765end