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