1##
2# This file contains stuff stolen outright from:
3#
4#   rtags.rb -
5#   ruby-lex.rb - ruby lexcal analyzer
6#   ruby-token.rb - ruby tokens
7#       by Keiju ISHITSUKA (Nippon Rational Inc.)
8#
9
10$TOKEN_DEBUG ||= nil
11
12##
13# Extracts code elements from a source file returning a TopLevel object
14# containing the constituent file elements.
15#
16# This file is based on rtags
17#
18# RubyParser understands how to document:
19# * classes
20# * modules
21# * methods
22# * constants
23# * aliases
24# * private, public, protected
25# * private_class_function, public_class_function
26# * module_function
27# * attr, attr_reader, attr_writer, attr_accessor
28# * extra accessors given on the command line
29# * metaprogrammed methods
30# * require
31# * include
32#
33# == Method Arguments
34#
35#--
36# NOTE: I don't think this works, needs tests, remove the paragraph following
37# this block when known to work
38#
39# The parser extracts the arguments from the method definition.  You can
40# override this with a custom argument definition using the :args: directive:
41#
42#   ##
43#   # This method tries over and over until it is tired
44#
45#   def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
46#     puts thing_to_try
47#     go_go_go thing_to_try, tries - 1
48#   end
49#
50# If you have a more-complex set of overrides you can use the :call-seq:
51# directive:
52#++
53#
54# The parser extracts the arguments from the method definition.  You can
55# override this with a custom argument definition using the :call-seq:
56# directive:
57#
58#   ##
59#   # This method can be called with a range or an offset and length
60#   #
61#   # :call-seq:
62#   #   my_method(Range)
63#   #   my_method(offset, length)
64#
65#   def my_method(*args)
66#   end
67#
68# The parser extracts +yield+ expressions from method bodies to gather the
69# yielded argument names.  If your method manually calls a block instead of
70# yielding or you want to override the discovered argument names use
71# the :yields: directive:
72#
73#   ##
74#   # My method is awesome
75#
76#   def my_method(&block) # :yields: happy, times
77#     block.call 1, 2
78#   end
79#
80# == Metaprogrammed Methods
81#
82# To pick up a metaprogrammed method, the parser looks for a comment starting
83# with '##' before an identifier:
84#
85#   ##
86#   # This is a meta-programmed method!
87#
88#   add_my_method :meta_method, :arg1, :arg2
89#
90# The parser looks at the token after the identifier to determine the name, in
91# this example, :meta_method.  If a name cannot be found, a warning is printed
92# and 'unknown is used.
93#
94# You can force the name of a method using the :method: directive:
95#
96#   ##
97#   # :method: some_method!
98#
99# By default, meta-methods are instance methods.  To indicate that a method is
100# a singleton method instead use the :singleton-method: directive:
101#
102#   ##
103#   # :singleton-method:
104#
105# You can also use the :singleton-method: directive with a name:
106#
107#   ##
108#   # :singleton-method: some_method!
109#
110# Additionally you can mark a method as an attribute by
111# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:.  Just like
112# for :method:, the name is optional.
113#
114#   ##
115#   # :attr_reader: my_attr_name
116#
117# == Hidden methods and attributes
118#
119# You can provide documentation for methods that don't appear using
120# the :method:, :singleton-method: and :attr: directives:
121#
122#   ##
123#   # :attr_writer: ghost_writer
124#   # There is an attribute here, but you can't see it!
125#
126#   ##
127#   # :method: ghost_method
128#   # There is a method here, but you can't see it!
129#
130#   ##
131#   # this is a comment for a regular method
132#
133#   def regular_method() end
134#
135# Note that by default, the :method: directive will be ignored if there is a
136# standard rdocable item following it.
137
138class RDoc::Parser::Ruby < RDoc::Parser
139
140  parse_files_matching(/\.rbw?$/)
141
142  include RDoc::RubyToken
143  include RDoc::TokenStream
144  include RDoc::Parser::RubyTools
145
146  ##
147  # RDoc::NormalClass type
148
149  NORMAL = "::"
150
151  ##
152  # RDoc::SingleClass type
153
154  SINGLE = "<<"
155
156  ##
157  # Creates a new Ruby parser.
158
159  def initialize(top_level, file_name, content, options, stats)
160    super
161
162    @size = 0
163    @token_listeners = nil
164    @scanner = RDoc::RubyLex.new content, @options
165    @scanner.exception_on_syntax_error = false
166    @prev_seek = nil
167    @markup = @options.markup
168
169    @encoding = nil
170    @encoding = @options.encoding if Object.const_defined? :Encoding
171
172    reset
173  end
174
175  ##
176  # Look for the first comment in a file that isn't a shebang line.
177
178  def collect_first_comment
179    skip_tkspace
180    comment = ''
181    comment.force_encoding @encoding if @encoding
182    first_line = true
183
184    tk = get_tk
185
186    while TkCOMMENT === tk
187      if first_line and tk.text =~ /\A#!/ then
188        skip_tkspace
189        tk = get_tk
190      elsif first_line and tk.text =~ /\A#\s*-\*-/ then
191        first_line = false
192        skip_tkspace
193        tk = get_tk
194      else
195        first_line = false
196        comment << tk.text << "\n"
197        tk = get_tk
198
199        if TkNL === tk then
200          skip_tkspace false
201          tk = get_tk
202        end
203      end
204    end
205
206    unget_tk tk
207
208    new_comment comment
209  end
210
211  ##
212  # Aborts with +msg+
213
214  def error(msg)
215    msg = make_message msg
216
217    abort msg
218  end
219
220  ##
221  # Looks for a true or false token.  Returns false if TkFALSE or TkNIL are
222  # found.
223
224  def get_bool
225    skip_tkspace
226    tk = get_tk
227    case tk
228    when TkTRUE
229      true
230    when TkFALSE, TkNIL
231      false
232    else
233      unget_tk tk
234      true
235    end
236  end
237
238  ##
239  # Look for the name of a class of module (optionally with a leading :: or
240  # with :: separated named) and return the ultimate name, the associated
241  # container, and the given name (with the ::).
242
243  def get_class_or_module container, ignore_constants = false
244    skip_tkspace
245    name_t = get_tk
246    given_name = ''
247
248    # class ::A -> A is in the top level
249    case name_t
250    when TkCOLON2, TkCOLON3 then # bug
251      name_t = get_tk
252      container = @top_level
253      given_name << '::'
254    end
255
256    skip_tkspace false
257    given_name << name_t.name
258
259    while TkCOLON2 === peek_tk do
260      prev_container = container
261      container = container.find_module_named name_t.name
262      container ||=
263        if ignore_constants then
264          RDoc::Context.new
265        else
266          c = prev_container.add_module RDoc::NormalModule, name_t.name
267          c.ignore unless prev_container.document_children
268          c
269        end
270
271      container.record_location @top_level
272
273      get_tk
274      skip_tkspace false
275      name_t = get_tk
276      given_name << '::' << name_t.name
277    end
278
279    skip_tkspace false
280
281    return [container, name_t, given_name]
282  end
283
284  ##
285  # Return a superclass, which can be either a constant of an expression
286
287  def get_class_specification
288    tk = get_tk
289    return 'self' if TkSELF === tk
290    return ''     if TkGVAR === tk
291
292    res = ''
293    while TkCOLON2 === tk or TkCOLON3 === tk or TkCONSTANT === tk do
294      res += tk.name
295      tk = get_tk
296    end
297
298    unget_tk(tk)
299    skip_tkspace false
300
301    get_tkread # empty out read buffer
302
303    tk = get_tk
304
305    case tk
306    when TkNL, TkCOMMENT, TkSEMICOLON then
307      unget_tk(tk)
308      return res
309    end
310
311    res += parse_call_parameters(tk)
312    res
313  end
314
315  ##
316  # Parse a constant, which might be qualified by one or more class or module
317  # names
318
319  def get_constant
320    res = ""
321    skip_tkspace false
322    tk = get_tk
323
324    while TkCOLON2 === tk or TkCOLON3 === tk or TkCONSTANT === tk do
325      res += tk.name
326      tk = get_tk
327    end
328
329#      if res.empty?
330#        warn("Unexpected token #{tk} in constant")
331#      end
332    unget_tk(tk)
333    res
334  end
335
336  ##
337  # Get a constant that may be surrounded by parens
338
339  def get_constant_with_optional_parens
340    skip_tkspace false
341
342    nest = 0
343
344    while TkLPAREN === (tk = peek_tk) or TkfLPAREN === tk do
345      get_tk
346      skip_tkspace
347      nest += 1
348    end
349
350    name = get_constant
351
352    while nest > 0
353      skip_tkspace
354      tk = get_tk
355      nest -= 1 if TkRPAREN === tk
356    end
357
358    name
359  end
360
361  ##
362  # Extracts a name or symbol from the token stream.
363
364  def get_symbol_or_name
365    tk = get_tk
366    case tk
367    when TkSYMBOL then
368      text = tk.text.sub(/^:/, '')
369
370      if TkASSIGN === peek_tk then
371        get_tk
372        text << '='
373      end
374
375      text
376    when TkId, TkOp then
377      tk.name
378    when TkAMPER,
379         TkDSTRING,
380         TkSTAR,
381         TkSTRING then
382      tk.text
383    else
384      raise RDoc::Error, "Name or symbol expected (got #{tk})"
385    end
386  end
387
388  ##
389  # Look for directives in a normal comment block:
390  #
391  #   # :stopdoc:
392  #   # Don't display comment from this point forward
393  #
394  # This routine modifies its +comment+ parameter.
395
396  def look_for_directives_in context, comment
397    @preprocess.handle comment, context do |directive, param|
398      case directive
399      when 'method', 'singleton-method',
400           'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then
401        false # handled elsewhere
402      when 'section' then
403        context.set_current_section param, comment.dup
404        comment.text = ''
405        break
406      end
407    end
408
409    remove_private_comments comment
410  end
411
412  ##
413  # Adds useful info about the parser to +message+
414
415  def make_message message
416    prefix = "#{@file_name}:"
417
418    prefix << "#{@scanner.line_no}:#{@scanner.char_no}:" if @scanner
419
420    "#{prefix} #{message}"
421  end
422
423  ##
424  # Creates a comment with the correct format
425
426  def new_comment comment
427    c = RDoc::Comment.new comment, @top_level
428    c.format = @markup
429    c
430  end
431
432  ##
433  # Creates an RDoc::Attr for the name following +tk+, setting the comment to
434  # +comment+.
435
436  def parse_attr(context, single, tk, comment)
437    offset  = tk.seek
438    line_no = tk.line_no
439
440    args = parse_symbol_arg 1
441    if args.size > 0 then
442      name = args[0]
443      rw = "R"
444      skip_tkspace false
445      tk = get_tk
446
447      if TkCOMMA === tk then
448        rw = "RW" if get_bool
449      else
450        unget_tk tk
451      end
452
453      att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE
454      att.record_location @top_level
455      att.offset = offset
456      att.line   = line_no
457
458      read_documentation_modifiers att, RDoc::ATTR_MODIFIERS
459
460      context.add_attribute att
461
462      @stats.add_attribute att
463    else
464      warn "'attr' ignored - looks like a variable"
465    end
466  end
467
468  ##
469  # Creates an RDoc::Attr for each attribute listed after +tk+, setting the
470  # comment for each to +comment+.
471
472  def parse_attr_accessor(context, single, tk, comment)
473    offset  = tk.seek
474    line_no = tk.line_no
475
476    args = parse_symbol_arg
477    rw = "?"
478
479    tmp = RDoc::CodeObject.new
480    read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS
481    # TODO In most other places we let the context keep track of document_self
482    # and add found items appropriately but here we do not.  I'm not sure why.
483    return unless tmp.document_self
484
485    case tk.name
486    when "attr_reader"   then rw = "R"
487    when "attr_writer"   then rw = "W"
488    when "attr_accessor" then rw = "RW"
489    else
490      rw = '?'
491    end
492
493    for name in args
494      att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE
495      att.record_location @top_level
496      att.offset = offset
497      att.line   = line_no
498
499      context.add_attribute att
500      @stats.add_attribute att
501    end
502  end
503
504  ##
505  # Parses an +alias+ in +context+ with +comment+
506
507  def parse_alias(context, single, tk, comment)
508    offset  = tk.seek
509    line_no = tk.line_no
510
511    skip_tkspace
512
513    if TkLPAREN === peek_tk then
514      get_tk
515      skip_tkspace
516    end
517
518    new_name = get_symbol_or_name
519
520    @scanner.instance_eval { @lex_state = EXPR_FNAME }
521
522    skip_tkspace
523    if TkCOMMA === peek_tk then
524      get_tk
525      skip_tkspace
526    end
527
528    begin
529      old_name = get_symbol_or_name
530    rescue RDoc::Error
531      return
532    end
533
534    al = RDoc::Alias.new(get_tkread, old_name, new_name, comment,
535                         single == SINGLE)
536    al.record_location @top_level
537    al.offset = offset
538    al.line   = line_no
539
540    read_documentation_modifiers al, RDoc::ATTR_MODIFIERS
541    context.add_alias al
542    @stats.add_alias al
543
544    al
545  end
546
547  ##
548  # Extracts call parameters from the token stream.
549
550  def parse_call_parameters(tk)
551    end_token = case tk
552                when TkLPAREN, TkfLPAREN
553                  TkRPAREN
554                when TkRPAREN
555                  return ""
556                else
557                  TkNL
558                end
559    nest = 0
560
561    loop do
562      case tk
563      when TkSEMICOLON
564        break
565      when TkLPAREN, TkfLPAREN
566        nest += 1
567      when end_token
568        if end_token == TkRPAREN
569          nest -= 1
570          break if @scanner.lex_state == EXPR_END and nest <= 0
571        else
572          break unless @scanner.continue
573        end
574      when TkCOMMENT, TkASSIGN, TkOPASGN
575        unget_tk(tk)
576        break
577      when nil then
578        break
579      end
580      tk = get_tk
581    end
582    res = get_tkread.tr("\n", " ").strip
583    res = "" if res == ";"
584    res
585  end
586
587  ##
588  # Parses a class in +context+ with +comment+
589
590  def parse_class container, single, tk, comment
591    offset  = tk.seek
592    line_no = tk.line_no
593
594    declaration_context = container
595    container, name_t, given_name = get_class_or_module container
596
597    case name_t
598    when TkCONSTANT
599      name = name_t.name
600      superclass = '::Object'
601
602      if given_name =~ /^::/ then
603        declaration_context = @top_level
604        given_name = $'
605      end
606
607      if TkLT === peek_tk then
608        get_tk
609        skip_tkspace
610        superclass = get_class_specification
611        superclass = '(unknown)' if superclass.empty?
612      end
613
614      cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass
615      cls = declaration_context.add_class cls_type, given_name, superclass
616      cls.ignore unless container.document_children
617
618      read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS
619      cls.record_location @top_level
620      cls.offset = offset
621      cls.line   = line_no
622
623      cls.add_comment comment, @top_level
624
625      @top_level.add_to_classes_or_modules cls
626      @stats.add_class cls
627
628      parse_statements cls
629    when TkLSHFT
630      case name = get_class_specification
631      when 'self', container.name
632        parse_statements container, SINGLE
633      else
634        other = @store.find_class_named name
635
636        unless other then
637          if name =~ /^::/ then
638            name = $'
639            container = @top_level
640          end
641
642          other = container.add_module RDoc::NormalModule, name
643          other.record_location @top_level
644          other.offset  = offset
645          other.line    = line_no
646
647          # class << $gvar
648          other.ignore if name.empty?
649
650          other.add_comment comment, @top_level
651        end
652
653        # notify :nodoc: all if not a constant-named class/module
654        # (and remove any comment)
655        unless name =~ /\A(::)?[A-Z]/ then
656          other.document_self = nil
657          other.document_children = false
658          other.clear_comment
659        end
660
661        @top_level.add_to_classes_or_modules other
662        @stats.add_class other
663
664        read_documentation_modifiers other, RDoc::CLASS_MODIFIERS
665        parse_statements(other, SINGLE)
666      end
667    else
668      warn("Expected class name or '<<'. Got #{name_t.class}: #{name_t.text.inspect}")
669    end
670  end
671
672  ##
673  # Parses a constant in +context+ with +comment+.  If +ignore_constants+ is
674  # true, no found constants will be added to RDoc.
675
676  def parse_constant container, tk, comment, ignore_constants = false
677    offset  = tk.seek
678    line_no = tk.line_no
679
680    name = tk.name
681    skip_tkspace false
682
683    return unless name =~ /^\w+$/
684
685    eq_tk = get_tk
686
687    if TkCOLON2 === eq_tk then
688      unget_tk eq_tk
689      unget_tk tk
690
691      container, name_t, = get_class_or_module container, ignore_constants
692
693      name = name_t.name
694
695      eq_tk = get_tk
696    end
697
698    unless TkASSIGN === eq_tk then
699      unget_tk eq_tk
700      return false
701    end
702
703    value = ''
704    con = RDoc::Constant.new name, value, comment
705    nest = 0
706    get_tkread
707
708    tk = get_tk
709
710    if TkGT === tk then
711      unget_tk tk
712      unget_tk eq_tk
713      return false
714    end
715
716    rhs_name = ''
717
718    loop do
719      case tk
720      when TkSEMICOLON then
721        break if nest <= 0
722      when TkLPAREN, TkfLPAREN, TkLBRACE, TkfLBRACE, TkLBRACK, TkfLBRACK,
723           TkDO, TkIF, TkUNLESS, TkCASE, TkDEF, TkBEGIN then
724        nest += 1
725      when TkRPAREN, TkRBRACE, TkRBRACK, TkEND then
726        nest -= 1
727      when TkCOMMENT then
728        if nest <= 0 &&
729           (@scanner.lex_state == EXPR_END || !@scanner.continue) then
730          unget_tk tk
731          break
732        else
733          unget_tk tk
734          read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS
735        end
736      when TkCONSTANT then
737        rhs_name << tk.name
738
739        if nest <= 0 and TkNL === peek_tk then
740          mod = if rhs_name =~ /^::/ then
741                  @store.find_class_or_module rhs_name
742                else
743                  container.find_module_named rhs_name
744                end
745
746          container.add_module_alias mod, name, @top_level if mod
747          break
748        end
749      when TkNL then
750        if nest <= 0 &&
751           (@scanner.lex_state == EXPR_END || !@scanner.continue) then
752          unget_tk tk
753          break
754        end
755      when TkCOLON2, TkCOLON3 then
756        rhs_name << '::'
757      when nil then
758        break
759      end
760      tk = get_tk
761    end
762
763    res = get_tkread.gsub(/^[ \t]+/, '').strip
764    res = "" if res == ";"
765
766    value.replace res
767    con.record_location @top_level
768    con.offset = offset
769    con.line   = line_no
770    read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS
771
772    @stats.add_constant con
773    con = container.add_constant con
774
775    true
776  end
777
778  ##
779  # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for
780  # :method: or :attr: directives in +comment+.
781
782  def parse_comment container, tk, comment
783    return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc'
784    column  = tk.char_no
785    offset  = tk.seek
786    line_no = tk.line_no
787
788    text = comment.text
789
790    singleton = !!text.sub!(/(^# +:?)(singleton-)(method:)/, '\1\3')
791
792    # REFACTOR
793    if text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then
794      name = $1 unless $1.empty?
795
796      meth = RDoc::GhostMethod.new get_tkread, name
797      meth.record_location @top_level
798      meth.singleton = singleton
799      meth.offset    = offset
800      meth.line      = line_no
801
802      meth.start_collecting_tokens
803      indent = TkSPACE.new 0, 1, 1
804      indent.set_text " " * column
805
806      position_comment = TkCOMMENT.new 0, line_no, 1
807      position_comment.set_text "# File #{@top_level.relative_name}, line #{line_no}"
808      meth.add_tokens [position_comment, NEWLINE_TOKEN, indent]
809
810      meth.params = ''
811
812      comment.normalize
813      comment.extract_call_seq meth
814
815      return unless meth.name
816
817      container.add_method meth
818
819      meth.comment = comment
820
821      @stats.add_method meth
822    elsif text.sub!(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '') then
823      rw = case $1
824           when 'attr_reader' then 'R'
825           when 'attr_writer' then 'W'
826           else 'RW'
827           end
828
829      name = $3 unless $3.empty?
830
831      # TODO authorize 'singleton-attr...'?
832      att = RDoc::Attr.new get_tkread, name, rw, comment
833      att.record_location @top_level
834      att.offset    = offset
835      att.line      = line_no
836
837      container.add_attribute att
838      @stats.add_attribute att
839    end
840
841    true
842  end
843
844  ##
845  # Creates an RDoc::Method on +container+ from +comment+ if there is a
846  # Signature section in the comment
847
848  def parse_comment_tomdoc container, tk, comment
849    return unless signature = RDoc::TomDoc.signature(comment)
850    offset  = tk.seek
851    line_no = tk.line_no
852
853    name, = signature.split %r%[ \(]%, 2
854
855    meth = RDoc::GhostMethod.new get_tkread, name
856    meth.record_location @top_level
857    meth.offset    = offset
858    meth.line      = line_no
859
860    meth.start_collecting_tokens
861    indent = TkSPACE.new 0, 1, 1
862    indent.set_text " " * offset
863
864    position_comment = TkCOMMENT.new 0, line_no, 1
865    position_comment.set_text "# File #{@top_level.relative_name}, line #{line_no}"
866    meth.add_tokens [position_comment, NEWLINE_TOKEN, indent]
867
868    meth.call_seq = signature
869
870    comment.normalize
871
872    return unless meth.name
873
874    container.add_method meth
875
876    meth.comment = comment
877
878    @stats.add_method meth
879  end
880
881  ##
882  # Parses an +include+ in +context+ with +comment+
883
884  def parse_include context, comment
885    loop do
886      skip_tkspace_comment
887
888      name = get_constant_with_optional_parens
889
890      unless name.empty? then
891        incl = context.add_include RDoc::Include.new(name, comment)
892        incl.record_location @top_level
893      end
894
895      return unless TkCOMMA === peek_tk
896
897      get_tk
898    end
899  end
900
901  ##
902  # Parses an +extend+ in +context+ with +comment+
903
904  def parse_extend context, comment
905    loop do
906      skip_tkspace_comment
907
908      name = get_constant_with_optional_parens
909
910      unless name.empty? then
911        incl = context.add_extend RDoc::Extend.new(name, comment)
912        incl.record_location @top_level
913      end
914
915      return unless TkCOMMA === peek_tk
916
917      get_tk
918    end
919  end
920
921  ##
922  # Parses a meta-programmed attribute and creates an RDoc::Attr.
923  #
924  # To create foo and bar attributes on class C with comment "My attributes":
925  #
926  #   class C
927  #
928  #     ##
929  #     # :attr:
930  #     #
931  #     # My attributes
932  #
933  #     my_attr :foo, :bar
934  #
935  #   end
936  #
937  # To create a foo attribute on class C with comment "My attribute":
938  #
939  #   class C
940  #
941  #     ##
942  #     # :attr: foo
943  #     #
944  #     # My attribute
945  #
946  #     my_attr :foo, :bar
947  #
948  #   end
949
950  def parse_meta_attr(context, single, tk, comment)
951    args = parse_symbol_arg
952    rw = "?"
953
954    # If nodoc is given, don't document any of them
955
956    tmp = RDoc::CodeObject.new
957    read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS
958
959    if comment.text.sub!(/^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '') then
960      rw = case $1
961           when 'attr_reader' then 'R'
962           when 'attr_writer' then 'W'
963           else 'RW'
964           end
965      name = $3 unless $3.empty?
966    end
967
968    if name then
969      att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE
970      att.record_location @top_level
971
972      context.add_attribute att
973      @stats.add_attribute att
974    else
975      args.each do |attr_name|
976        att = RDoc::Attr.new(get_tkread, attr_name, rw, comment,
977                             single == SINGLE)
978        att.record_location @top_level
979
980        context.add_attribute att
981        @stats.add_attribute att
982      end
983    end
984
985    att
986  end
987
988  ##
989  # Parses a meta-programmed method
990
991  def parse_meta_method(container, single, tk, comment)
992    column  = tk.char_no
993    offset  = tk.seek
994    line_no = tk.line_no
995
996    start_collecting_tokens
997    add_token tk
998    add_token_listener self
999
1000    skip_tkspace false
1001
1002    singleton = !!comment.text.sub!(/(^# +:?)(singleton-)(method:)/, '\1\3')
1003
1004    if comment.text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then
1005      name = $1 unless $1.empty?
1006    end
1007
1008    if name.nil? then
1009      name_t = get_tk
1010      case name_t
1011      when TkSYMBOL then
1012        name = name_t.text[1..-1]
1013      when TkSTRING then
1014        name = name_t.value[1..-2]
1015      when TkASSIGN then # ignore
1016        remove_token_listener self
1017        return
1018      else
1019        warn "unknown name token #{name_t.inspect} for meta-method '#{tk.name}'"
1020        name = 'unknown'
1021      end
1022    end
1023
1024    meth = RDoc::MetaMethod.new get_tkread, name
1025    meth.record_location @top_level
1026    meth.offset = offset
1027    meth.line   = line_no
1028    meth.singleton = singleton
1029
1030    remove_token_listener self
1031
1032    meth.start_collecting_tokens
1033    indent = TkSPACE.new 0, 1, 1
1034    indent.set_text " " * column
1035
1036    position_comment = TkCOMMENT.new 0, line_no, 1
1037    position_comment.value = "# File #{@top_level.relative_name}, line #{line_no}"
1038    meth.add_tokens [position_comment, NEWLINE_TOKEN, indent]
1039    meth.add_tokens @token_stream
1040
1041    token_listener meth do
1042      meth.params = ''
1043
1044      comment.normalize
1045      comment.extract_call_seq meth
1046
1047      container.add_method meth
1048
1049      last_tk = tk
1050
1051      while tk = get_tk do
1052        case tk
1053        when TkSEMICOLON then
1054          break
1055        when TkNL then
1056          break unless last_tk and TkCOMMA === last_tk
1057        when TkSPACE then
1058          # expression continues
1059        when TkDO then
1060          parse_statements container, single, meth
1061          break
1062        else
1063          last_tk = tk
1064        end
1065      end
1066    end
1067
1068    meth.comment = comment
1069
1070    @stats.add_method meth
1071
1072    meth
1073  end
1074
1075  ##
1076  # Parses a normal method defined by +def+
1077
1078  def parse_method(container, single, tk, comment)
1079    added_container = nil
1080    meth = nil
1081    name = nil
1082    column  = tk.char_no
1083    offset  = tk.seek
1084    line_no = tk.line_no
1085
1086    start_collecting_tokens
1087    add_token tk
1088
1089    token_listener self do
1090      @scanner.instance_eval do @lex_state = EXPR_FNAME end
1091
1092      skip_tkspace
1093      name_t = get_tk
1094      back_tk = skip_tkspace
1095      meth = nil
1096      added_container = false
1097
1098      case dot = get_tk
1099      when TkDOT, TkCOLON2 then
1100        @scanner.instance_eval do @lex_state = EXPR_FNAME end
1101        skip_tkspace
1102        name_t2 = get_tk
1103
1104        case name_t
1105        when TkSELF, TkMOD then
1106          name = case name_t2
1107                 # NOTE: work around '[' being consumed early and not being
1108                 # re-tokenized as a TkAREF
1109                 when TkfLBRACK then
1110                   get_tk
1111                   '[]'
1112                 else
1113                   name_t2.name
1114                 end
1115        when TkCONSTANT then
1116          name = name_t2.name
1117          prev_container = container
1118          container = container.find_module_named(name_t.name)
1119
1120          unless container then
1121            constant = prev_container.constants.find do |const|
1122              const.name == name_t.name
1123            end
1124
1125            if constant then
1126              parse_method_dummy prev_container
1127              return
1128            end
1129          end
1130
1131          unless container then
1132            added_container = true
1133            obj = name_t.name.split("::").inject(Object) do |state, item|
1134              state.const_get(item)
1135            end rescue nil
1136
1137            type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule
1138
1139            unless [Class, Module].include?(obj.class) then
1140              warn("Couldn't find #{name_t.name}. Assuming it's a module")
1141            end
1142
1143            if type == RDoc::NormalClass then
1144              sclass = obj.superclass ? obj.superclass.name : nil
1145              container = prev_container.add_class type, name_t.name, sclass
1146            else
1147              container = prev_container.add_module type, name_t.name
1148            end
1149
1150            container.record_location @top_level
1151          end
1152        when TkIDENTIFIER, TkIVAR, TkGVAR then
1153          parse_method_dummy container
1154          return
1155        when TkTRUE, TkFALSE, TkNIL then
1156          klass_name = "#{name_t.name.capitalize}Class"
1157          container = @store.find_class_named klass_name
1158          container ||= @top_level.add_class RDoc::NormalClass, klass_name
1159
1160          name = name_t2.name
1161        else
1162          warn "unexpected method name token #{name_t.inspect}"
1163          # break
1164          skip_method container
1165          return
1166        end
1167
1168        meth = RDoc::AnyMethod.new(get_tkread, name)
1169        meth.singleton = true
1170      else
1171        unget_tk dot
1172        back_tk.reverse_each do |token|
1173          unget_tk token
1174        end
1175
1176        name = case name_t
1177               when TkSTAR, TkAMPER then
1178                 name_t.text
1179               else
1180                 unless name_t.respond_to? :name then
1181                   warn "expected method name token, . or ::, got #{name_t.inspect}"
1182                   skip_method container
1183                   return
1184                 end
1185                 name_t.name
1186               end
1187
1188        meth = RDoc::AnyMethod.new get_tkread, name
1189        meth.singleton = (single == SINGLE)
1190      end
1191    end
1192
1193    meth.record_location @top_level
1194    meth.offset = offset
1195    meth.line   = line_no
1196
1197    meth.start_collecting_tokens
1198    indent = TkSPACE.new 0, 1, 1
1199    indent.set_text " " * column
1200
1201    token = TkCOMMENT.new 0, line_no, 1
1202    token.set_text "# File #{@top_level.relative_name}, line #{line_no}"
1203    meth.add_tokens [token, NEWLINE_TOKEN, indent]
1204    meth.add_tokens @token_stream
1205
1206    token_listener meth do
1207      @scanner.instance_eval do @continue = false end
1208      parse_method_parameters meth
1209
1210      if meth.document_self then
1211        container.add_method meth
1212      elsif added_container then
1213        container.document_self = false
1214      end
1215
1216      # Having now read the method parameters and documentation modifiers, we
1217      # now know whether we have to rename #initialize to ::new
1218
1219      if name == "initialize" && !meth.singleton then
1220        if meth.dont_rename_initialize then
1221          meth.visibility = :protected
1222        else
1223          meth.singleton = true
1224          meth.name = "new"
1225          meth.visibility = :public
1226        end
1227      end
1228
1229      parse_statements container, single, meth
1230    end
1231
1232    comment.normalize
1233    comment.extract_call_seq meth
1234
1235    meth.comment = comment
1236
1237    @stats.add_method meth
1238  end
1239
1240  ##
1241  # Parses a method that needs to be ignored.
1242
1243  def parse_method_dummy container
1244    dummy = RDoc::Context.new
1245    dummy.parent = container
1246    dummy.store  = container.store
1247    skip_method dummy
1248  end
1249
1250  ##
1251  # Extracts +yield+ parameters from +method+
1252
1253  def parse_method_or_yield_parameters(method = nil,
1254                                       modifiers = RDoc::METHOD_MODIFIERS)
1255    skip_tkspace false
1256    tk = get_tk
1257
1258    # Little hack going on here. In the statement
1259    #  f = 2*(1+yield)
1260    # We see the RPAREN as the next token, so we need
1261    # to exit early. This still won't catch all cases
1262    # (such as "a = yield + 1"
1263    end_token = case tk
1264                when TkLPAREN, TkfLPAREN
1265                  TkRPAREN
1266                when TkRPAREN
1267                  return ""
1268                else
1269                  TkNL
1270                end
1271    nest = 0
1272
1273    loop do
1274      case tk
1275      when TkSEMICOLON then
1276        break if nest == 0
1277      when TkLBRACE, TkfLBRACE then
1278        nest += 1
1279      when TkRBRACE then
1280        nest -= 1
1281        if nest <= 0
1282          # we might have a.each { |i| yield i }
1283          unget_tk(tk) if nest < 0
1284          break
1285        end
1286      when TkLPAREN, TkfLPAREN then
1287        nest += 1
1288      when end_token then
1289        if end_token == TkRPAREN
1290          nest -= 1
1291          break if nest <= 0
1292        else
1293          break unless @scanner.continue
1294        end
1295      when TkRPAREN then
1296        nest -= 1
1297      when method && method.block_params.nil? && TkCOMMENT then
1298        unget_tk tk
1299        read_documentation_modifiers method, modifiers
1300        @read.pop
1301      when TkCOMMENT then
1302        @read.pop
1303      when nil then
1304        break
1305      end
1306      tk = get_tk
1307    end
1308
1309    res = get_tkread.gsub(/\s+/, ' ').strip
1310    res = '' if res == ';'
1311    res
1312  end
1313
1314  ##
1315  # Capture the method's parameters. Along the way, look for a comment
1316  # containing:
1317  #
1318  #    # yields: ....
1319  #
1320  # and add this as the block_params for the method
1321
1322  def parse_method_parameters method
1323    res = parse_method_or_yield_parameters method
1324
1325    res = "(#{res})" unless res =~ /\A\(/
1326    method.params = res unless method.params
1327
1328    return if  method.block_params
1329
1330    skip_tkspace false
1331    read_documentation_modifiers method, RDoc::METHOD_MODIFIERS
1332  end
1333
1334  ##
1335  # Parses an RDoc::NormalModule in +container+ with +comment+
1336
1337  def parse_module container, single, tk, comment
1338    container, name_t, = get_class_or_module container
1339
1340    name = name_t.name
1341
1342    mod = container.add_module RDoc::NormalModule, name
1343    mod.ignore unless container.document_children
1344    mod.record_location @top_level
1345
1346    read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
1347    mod.add_comment comment, @top_level
1348    parse_statements mod
1349
1350    @top_level.add_to_classes_or_modules mod
1351    @stats.add_module mod
1352  end
1353
1354  ##
1355  # Parses an RDoc::Require in +context+ containing +comment+
1356
1357  def parse_require(context, comment)
1358    skip_tkspace_comment
1359    tk = get_tk
1360
1361    if TkLPAREN === tk then
1362      skip_tkspace_comment
1363      tk = get_tk
1364    end
1365
1366    name = tk.text if TkSTRING === tk
1367
1368    if name then
1369      @top_level.add_require RDoc::Require.new(name, comment)
1370    else
1371      unget_tk tk
1372    end
1373  end
1374
1375  ##
1376  # Parses a rescue
1377
1378  def parse_rescue
1379    skip_tkspace false
1380
1381    while tk = get_tk
1382      case tk
1383      when TkNL, TkSEMICOLON then
1384        break
1385      when TkCOMMA then
1386        skip_tkspace false
1387
1388        get_tk if TkNL === peek_tk
1389      end
1390
1391      skip_tkspace false
1392    end
1393  end
1394
1395  ##
1396  # The core of the ruby parser.
1397
1398  def parse_statements(container, single = NORMAL, current_method = nil,
1399                       comment = new_comment(''))
1400    raise 'no' unless RDoc::Comment === comment
1401    comment.force_encoding @encoding if @encoding
1402
1403    nest = 1
1404    save_visibility = container.visibility
1405
1406    non_comment_seen = true
1407
1408    while tk = get_tk do
1409      keep_comment = false
1410      try_parse_comment = false
1411
1412      non_comment_seen = true unless TkCOMMENT === tk
1413
1414      case tk
1415      when TkNL then
1416        skip_tkspace
1417        tk = get_tk
1418
1419        if TkCOMMENT === tk then
1420          if non_comment_seen then
1421            # Look for RDoc in a comment about to be thrown away
1422            non_comment_seen = parse_comment container, tk, comment unless
1423              comment.empty?
1424
1425            comment = ''
1426            comment.force_encoding @encoding if @encoding
1427          end
1428
1429          while TkCOMMENT === tk do
1430            comment << tk.text << "\n"
1431
1432            tk = get_tk
1433
1434            if TkNL === tk then
1435              skip_tkspace false # leading spaces
1436              tk = get_tk
1437            end
1438          end
1439
1440          comment = new_comment comment
1441
1442          unless comment.empty? then
1443            look_for_directives_in container, comment
1444
1445            if container.done_documenting then
1446              container.ongoing_visibility = save_visibility
1447            end
1448          end
1449
1450          keep_comment = true
1451        else
1452          non_comment_seen = true
1453        end
1454
1455        unget_tk tk
1456        keep_comment = true
1457
1458      when TkCLASS then
1459        parse_class container, single, tk, comment
1460
1461      when TkMODULE then
1462        parse_module container, single, tk, comment
1463
1464      when TkDEF then
1465        parse_method container, single, tk, comment
1466
1467      when TkCONSTANT then
1468        unless parse_constant container, tk, comment, current_method then
1469          try_parse_comment = true
1470        end
1471
1472      when TkALIAS then
1473        parse_alias container, single, tk, comment unless current_method
1474
1475      when TkYIELD then
1476        if current_method.nil? then
1477          warn "Warning: yield outside of method" if container.document_self
1478        else
1479          parse_yield container, single, tk, current_method
1480        end
1481
1482      # Until and While can have a 'do', which shouldn't increase the nesting.
1483      # We can't solve the general case, but we can handle most occurrences by
1484      # ignoring a do at the end of a line.
1485
1486      when TkUNTIL, TkWHILE then
1487        nest += 1
1488        skip_optional_do_after_expression
1489
1490      # 'for' is trickier
1491      when TkFOR then
1492        nest += 1
1493        skip_for_variable
1494        skip_optional_do_after_expression
1495
1496      when TkCASE, TkDO, TkIF, TkUNLESS, TkBEGIN then
1497        nest += 1
1498
1499      when TkSUPER then
1500        current_method.calls_super = true if current_method
1501
1502      when TkRESCUE then
1503        parse_rescue
1504
1505      when TkIDENTIFIER then
1506        if nest == 1 and current_method.nil? then
1507          case tk.name
1508          when 'private', 'protected', 'public', 'private_class_method',
1509               'public_class_method', 'module_function' then
1510            parse_visibility container, single, tk
1511            keep_comment = true
1512          when 'attr' then
1513            parse_attr container, single, tk, comment
1514          when /^attr_(reader|writer|accessor)$/ then
1515            parse_attr_accessor container, single, tk, comment
1516          when 'alias_method' then
1517            parse_alias container, single, tk, comment
1518          when 'require', 'include' then
1519            # ignore
1520          else
1521            if comment.text =~ /\A#\#$/ then
1522              case comment.text
1523              when /^# +:?attr(_reader|_writer|_accessor)?:/ then
1524                parse_meta_attr container, single, tk, comment
1525              else
1526                method = parse_meta_method container, single, tk, comment
1527                method.params = container.params if
1528                  container.params
1529                method.block_params = container.block_params if
1530                  container.block_params
1531              end
1532            end
1533          end
1534        end
1535
1536        case tk.name
1537        when "require" then
1538          parse_require container, comment
1539        when "include" then
1540          parse_include container, comment
1541        when "extend" then
1542          parse_extend container, comment
1543        end
1544
1545      when TkEND then
1546        nest -= 1
1547        if nest == 0 then
1548          read_documentation_modifiers container, RDoc::CLASS_MODIFIERS
1549          container.ongoing_visibility = save_visibility
1550
1551          parse_comment container, tk, comment unless comment.empty?
1552
1553          return
1554        end
1555      else
1556        try_parse_comment = nest == 1
1557      end
1558
1559      if try_parse_comment then
1560        non_comment_seen = parse_comment container, tk, comment unless
1561          comment.empty?
1562
1563        keep_comment = false
1564      end
1565
1566      unless keep_comment then
1567        comment = new_comment ''
1568        comment.force_encoding @encoding if @encoding
1569        container.params = nil
1570        container.block_params = nil
1571      end
1572
1573      begin
1574        get_tkread
1575        skip_tkspace false
1576      end while peek_tk == TkNL
1577    end
1578
1579    container.params = nil
1580    container.block_params = nil
1581  end
1582
1583  ##
1584  # Parse up to +no+ symbol arguments
1585
1586  def parse_symbol_arg(no = nil)
1587    args = []
1588
1589    skip_tkspace_comment
1590
1591    case tk = get_tk
1592    when TkLPAREN
1593      loop do
1594        skip_tkspace_comment
1595        if tk1 = parse_symbol_in_arg
1596          args.push tk1
1597          break if no and args.size >= no
1598        end
1599
1600        skip_tkspace_comment
1601        case tk2 = get_tk
1602        when TkRPAREN
1603          break
1604        when TkCOMMA
1605        else
1606          warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC
1607          break
1608        end
1609      end
1610    else
1611      unget_tk tk
1612      if tk = parse_symbol_in_arg
1613        args.push tk
1614        return args if no and args.size >= no
1615      end
1616
1617      loop do
1618        skip_tkspace false
1619
1620        tk1 = get_tk
1621        unless TkCOMMA === tk1 then
1622          unget_tk tk1
1623          break
1624        end
1625
1626        skip_tkspace_comment
1627        if tk = parse_symbol_in_arg
1628          args.push tk
1629          break if no and args.size >= no
1630        end
1631      end
1632    end
1633
1634    args
1635  end
1636
1637  ##
1638  # Returns symbol text from the next token
1639
1640  def parse_symbol_in_arg
1641    case tk = get_tk
1642    when TkSYMBOL
1643      tk.text.sub(/^:/, '')
1644    when TkSTRING
1645      eval @read[-1]
1646    when TkDSTRING, TkIDENTIFIER then
1647      nil # ignore
1648    else
1649      warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC
1650      nil
1651    end
1652  end
1653
1654  ##
1655  # Parses statements in the top-level +container+
1656
1657  def parse_top_level_statements container
1658    comment = collect_first_comment
1659
1660    look_for_directives_in container, comment
1661
1662    @markup = comment.format
1663
1664    # HACK move if to RDoc::Context#comment=
1665    container.comment = comment if container.document_self unless comment.empty?
1666
1667    parse_statements container, NORMAL, nil, comment
1668  end
1669
1670  ##
1671  # Determines the visibility in +container+ from +tk+
1672
1673  def parse_visibility(container, single, tk)
1674    singleton = (single == SINGLE)
1675
1676    vis_type = tk.name
1677
1678    vis = case vis_type
1679          when 'private'   then :private
1680          when 'protected' then :protected
1681          when 'public'    then :public
1682          when 'private_class_method' then
1683            singleton = true
1684            :private
1685          when 'public_class_method' then
1686            singleton = true
1687            :public
1688          when 'module_function' then
1689            singleton = true
1690            :public
1691          else
1692            raise RDoc::Error, "Invalid visibility: #{tk.name}"
1693          end
1694
1695    skip_tkspace_comment false
1696
1697    case peek_tk
1698      # Ryan Davis suggested the extension to ignore modifiers, because he
1699      # often writes
1700      #
1701      #   protected unless $TESTING
1702      #
1703    when TkNL, TkUNLESS_MOD, TkIF_MOD, TkSEMICOLON then
1704      container.ongoing_visibility = vis
1705    else
1706      new_methods = []
1707
1708      case vis_type
1709      when 'module_function' then
1710        args = parse_symbol_arg
1711        container.set_visibility_for args, :private, false
1712
1713        container.methods_matching args do |m|
1714          s_m = m.dup
1715          s_m.record_location @top_level
1716          s_m.singleton = true
1717          new_methods << s_m
1718        end
1719      when 'public_class_method', 'private_class_method' then
1720        args = parse_symbol_arg
1721
1722        container.methods_matching args, true do |m|
1723          if m.parent != container then
1724            m = m.dup
1725            m.record_location @top_level
1726            new_methods << m
1727          end
1728
1729          m.visibility = vis
1730        end
1731      else
1732        args = parse_symbol_arg
1733        container.set_visibility_for args, vis, singleton
1734      end
1735
1736      new_methods.each do |method|
1737        case method
1738        when RDoc::AnyMethod then
1739          container.add_method method
1740        when RDoc::Attr then
1741          container.add_attribute method
1742        end
1743        method.visibility = vis
1744      end
1745    end
1746  end
1747
1748  ##
1749  # Determines the block parameter for +context+
1750
1751  def parse_yield(context, single, tk, method)
1752    return if method.block_params
1753
1754    get_tkread
1755    @scanner.instance_eval { @continue = false }
1756    method.block_params = parse_method_or_yield_parameters
1757  end
1758
1759  ##
1760  # Directives are modifier comments that can appear after class, module, or
1761  # method names. For example:
1762  #
1763  #   def fred # :yields: a, b
1764  #
1765  # or:
1766  #
1767  #   class MyClass # :nodoc:
1768  #
1769  # We return the directive name and any parameters as a two element array if
1770  # the name is in +allowed+.  A directive can be found anywhere up to the end
1771  # of the current line.
1772
1773  def read_directive allowed
1774    tokens = []
1775
1776    while tk = get_tk do
1777      tokens << tk
1778
1779      case tk
1780      when TkNL      then return
1781      when TkCOMMENT then
1782        return unless tk.text =~ /\s*:?([\w-]+):\s*(.*)/
1783
1784        directive = $1.downcase
1785
1786        return [directive, $2] if allowed.include? directive
1787
1788        return
1789      end
1790    end
1791  ensure
1792    unless tokens.length == 1 and TkCOMMENT === tokens.first then
1793      tokens.reverse_each do |token|
1794        unget_tk token
1795      end
1796    end
1797  end
1798
1799  ##
1800  # Handles directives following the definition for +context+ (any
1801  # RDoc::CodeObject) if the directives are +allowed+ at this point.
1802  #
1803  # See also RDoc::Markup::PreProcess#handle_directive
1804
1805  def read_documentation_modifiers context, allowed
1806    directive, value = read_directive allowed
1807
1808    return unless directive
1809
1810    @preprocess.handle_directive '', directive, value, context do |dir, param|
1811      if %w[notnew not_new not-new].include? dir then
1812        context.dont_rename_initialize = true
1813
1814        true
1815      end
1816    end
1817  end
1818
1819  ##
1820  # Removes private comments from +comment+
1821  #--
1822  # TODO remove
1823
1824  def remove_private_comments comment
1825    comment.remove_private
1826  end
1827
1828  ##
1829  # Scans this ruby file for ruby constructs
1830
1831  def scan
1832    reset
1833
1834    catch :eof do
1835      begin
1836        parse_top_level_statements @top_level
1837
1838      rescue StandardError => e
1839        bytes = ''
1840
1841        20.times do @scanner.ungetc end
1842        count = 0
1843        60.times do |i|
1844          count = i
1845          byte = @scanner.getc
1846          break unless byte
1847          bytes << byte
1848        end
1849        count -= 20
1850        count.times do @scanner.ungetc end
1851
1852        $stderr.puts <<-EOF
1853
1854#{self.class} failure around line #{@scanner.line_no} of
1855#{@file_name}
1856
1857        EOF
1858
1859        unless bytes.empty? then
1860          $stderr.puts
1861          $stderr.puts bytes.inspect
1862        end
1863
1864        raise e
1865      end
1866    end
1867
1868    @top_level
1869  end
1870
1871  ##
1872  # while, until, and for have an optional do
1873
1874  def skip_optional_do_after_expression
1875    skip_tkspace false
1876    tk = get_tk
1877    case tk
1878    when TkLPAREN, TkfLPAREN then
1879      end_token = TkRPAREN
1880    else
1881      end_token = TkNL
1882    end
1883
1884    b_nest = 0
1885    nest = 0
1886    @scanner.instance_eval { @continue = false }
1887
1888    loop do
1889      case tk
1890      when TkSEMICOLON then
1891        break if b_nest.zero?
1892      when TkLPAREN, TkfLPAREN then
1893        nest += 1
1894      when TkBEGIN then
1895        b_nest += 1
1896      when TkEND then
1897        b_nest -= 1
1898      when TkDO
1899        break if nest.zero?
1900      when end_token then
1901        if end_token == TkRPAREN
1902          nest -= 1
1903          break if @scanner.lex_state == EXPR_END and nest.zero?
1904        else
1905          break unless @scanner.continue
1906        end
1907      when nil then
1908        break
1909      end
1910      tk = get_tk
1911    end
1912
1913    skip_tkspace false
1914
1915    get_tk if TkDO === peek_tk
1916  end
1917
1918  ##
1919  # skip the var [in] part of a 'for' statement
1920
1921  def skip_for_variable
1922    skip_tkspace false
1923    tk = get_tk
1924    skip_tkspace false
1925    tk = get_tk
1926    unget_tk(tk) unless TkIN === tk
1927  end
1928
1929  ##
1930  # Skips the next method in +container+
1931
1932  def skip_method container
1933    meth = RDoc::AnyMethod.new "", "anon"
1934    parse_method_parameters meth
1935    parse_statements container, false, meth
1936  end
1937
1938  ##
1939  # Skip spaces until a comment is found
1940
1941  def skip_tkspace_comment(skip_nl = true)
1942    loop do
1943      skip_tkspace skip_nl
1944      return unless TkCOMMENT === peek_tk
1945      get_tk
1946    end
1947  end
1948
1949  ##
1950  # Prints +message+ to +$stderr+ unless we're being quiet
1951
1952  def warn message
1953    @options.warn make_message message
1954  end
1955
1956end
1957
1958