#= parse_f95.rb - Fortran95 Parser
#
#== 概要
#
#parse_f95.rb は拡張子が f90, F90, f95, F95 のファイルを構文分析します.
#ソースコードは Fortran 95 規格で推奨される書法で記述されることを
#前提としています.
#
#== Overview
#
#"parse_f95.rb" parses Fortran95 files with suffixes "f90", "F90", "f95"
#and "F95". Fortran95 files are expected to be conformed to Fortran95
#standards.
#
#== 書法
#
#基本的な書法は{Ruby の書法}[http://www.ruby-doc.org/stdlib/libdoc/rdoc/rdoc/index.html]
#と同様ですが, コメント部は '#' ではなく '!' で始まることに注意してください.
#
#== Rules
#
#Fundamental rules are same as that of the Ruby parser.
#But comment markers are '!' not '#'.
#
#=== RDoc ドキュメンテーションと Fortran95 プログラムとの対応
#
#parse_f95.rb はメインプログラム, モジュール, サブルーチン, 関数の他,
#public 指定によって公開される変数や定数, そして利用者定義演算子,
#利用者定義代入を構文分析します.
#これらの要素は RDOC ドキュメンテーションの各項目に,
#以下のように記述されます.
#
#Files :: ファイル (Ruby と同様です)
#Classes :: モジュール
#Methods :: サブルーチン, 関数, 変数, 定数, 構造体, 利用者定義演算子, 利用者定義代入
#Required files :: 参照するモジュールが定義されているファイル, 及び参照する外部関数が定義されているファイルのリスト
#Included Modules :: 参照するモジュールのリスト
#Attributes :: 構造体のリスト, 参照し且つその全ての要素を公開するモジュールのリスト
#
#'Methods' にリストされる要素 (サブルーチン, 関数, …) は,
#モジュール内で定義されているのであれば, 'Classes' の項目に記述されます.
#メインプログラム内で定義されたり, 外部手続きとして定義される場合には
#'Files' の項目に記述されます.
#
#
#=== Correspondence between RDoc documentation and Fortran95 programs
#
#"parse_f95.rb" parses main programs, modules, subroutines, functions,
#derived-types, public variables, public constants,
#defined operators and defined assignments.
#These components are described in items of RDoc documentation, as follows.
#
#Files :: Files (same as Ruby)
#Classes :: Modules
#Methods :: Subroutines, functions, variables, constants, derived-types, defined operators, defined assignments
#Required files :: Files in which imported modules, external subroutines and external functions are defined.
#Included Modules :: List of imported modules
#Attributes :: List of derived-types, List of imported modules all of whose components are published again
#
#Components listed in 'Methods' (subroutines, functions, ...)
#defined in modules are described in the item of 'Classes'.
#On the other hand, components defined in main programs or
#as external procedures are described in the item of 'Files'.
#
#=== デフォルトで構文分析される要素
#
#デフォルトでは, public 属性を持ち, 外部から参照が可能なサブルーチン,
#関数, 変数, 定数, 構造体, 利用者定義演算子,
#利用者定義代入のみドキュメントとして出力されます. --all
#オプションをつけて rdoc を実行する場合には private 属性を
#明示的に指定されるものも含め, 全てが出力されます.
#
#
#=== Components parsed by default
#
#By default, documentation on public components (subroutines, functions, 
#variables, constants, derived-types, defined operators, 
#defined assignments) are generated. 
#With "--all" option, documentation on all components
#are generated (almost same as the Ruby parser).
#
#
#=== 自動的に構文分析される情報
#
#以下の情報は, 自動的に構文分析されてコメント部として出力されます.
#
#* サブルーチン, 関数の引数の型
#* 公開変数, 公開定数の自身の型
#* 構造体の要素の型, 初期値
#* NAMELIST 変数とその要素の型, 初期値
#
#interface によって別名を指定する場合, その別名も 'Methods' に
#追加します.
#
#参照した別のモジュール内の公開要素 (サブルーチン, 関数, …)
#を自身の要素として改めて公開する場合,
#その要素は自身の要素としてモジュールの 'Methods' リストに追加されます.
#
#interface によって外部副プログラムを自身の副プログラムとして公開する
#場合, その外部副プログラムもモジュールの 'Methods' リストに追加します.
#
#
#=== Information parsed automatically
#
#The following information is automatically parsed.
#
#* Types of arguments
#* Types of variables and constants
#* Types of variables in the derived types, and initial values
#* NAMELISTs and types of variables in them, and initial values
#
#Aliases by interface statement are described in the item of 'Methods'.
#
#Components which are imported from other modules and published again 
#are described in the item of 'Methods'.
#
#
#=== コメント部の書き方
#
#コメント部は以下のように記述します. 原則的に行末または Fortran
#の文の下部にコメントを記述します. '!' を含まない行が現れた時点で,
#コメント部は終了したと見なされます.
#例ではコメント行もインデントされていますが, インデントすることは
#必須ではありません.
#
#     ! (ファイルの行頭)
#     !
#     ! ファイルに対するコメントはここに記述します
#     !
#     module hogehoge
#       !
#       ! モジュール (program の場合はメインルーチン)
#       ! に対するコメントはここに記述します
#       !
#
#       private
#
#       logical            :: a     ! 非公開変数
#       real, public       :: b     ! 公開変数
#       integer, parameter :: c = 0 ! 公開定数
#
#       public :: c
#       public :: MULTI_ARRAY
#       public :: hoge, foo
#
#       type MULTI_ARRAY
#         !
#         ! 構造体に対するコメントはここに記述します.
#         !
#         real, pointer :: var(:) =>null() ! 変数に対するコメント
#         integer       :: num = 0
#       end type MULTI_ARRAY
#
#     contains
#
#       subroutine hoge( in,   &   ! 継続行中のコメントは無視されます.
#         &              out )
#         !
#         ! サブルーチンや関数に対するコメントはここに記述します
#         !
#         character(*),intent(in):: in ! 引数に対するコメント
#         character(*),intent(out),allocatable,target  :: in
#                                       ! 下の行に記述する
#                                       ! ことも可能です.
#
#         character(32) :: file ! NAMELIST 内の変数として構文分析されます
#         integer       :: id
#
#         namelist /varinfo_nml/ file, id
#                 !
#                 ! NAMELIST に関する情報はここに記述します. 変数に
#                 ! 関する情報は, 変数定義部分を参照します.
#                 !
#
#       ....
#
#       end subroutine hoge
#
#       integer function foo( in )
#         !
#         ! この部分はコメントとして識別されます.
#
#         ! '!' の間隔を空けると以降はコメントとして扱われません.
#         ! つまりこの 2 行はコメントとして扱われません.
#         !
#         integer, intent(in):: inA ! コメントとして識別されます
#
#                                   ! コメントとして識別されません.
#
#       end function foo
#
#       subroutine hide( in,   &
#         &              out )      !:nodoc:
#         !
#         ! 上記のように, subroutine 文の末尾に
#         ! "!:nodoc:" と記述することで, そのサブルーチン
#         ! はドキュメントに反映されなくなります.
#         ! この指定は, モジュール, サブルーチン, 関数, 変数, 定数,
#         ! 構造体, 利用者定義演算子, 利用者定義代入,
#         ! 参照するモジュールのリスト (use 文) に対して有効です.
#         !
#
#       ....
#
#       end subroutine hide
#
#     end module hogehoge
#
#=== Format of comment blocks
#
#Comment blocks should be written as follows.
#Comment blocks are considered to be ended when the line without '!'
#appears.
#The indentation is not necessary.
#
#     ! (Top of file)
#     !
#     ! Comment blocks for the files.
#     !
#     module hogehoge
#       !
#       ! Comment blocks for the modules (or the programs).
#       !
#
#       private
#
#       logical            :: a     ! a private variable
#       real, public       :: b     ! a public variable
#       integer, parameter :: c = 0 ! a public constant
#
#       public :: c
#       public :: MULTI_ARRAY
#       public :: hoge, foo
#
#       type MULTI_ARRAY
#         !
#         ! Comment blocks for the derived-types.
#         !
#         real, pointer :: var(:) =>null() ! Comments block for the variables.
#         integer       :: num = 0
#       end type MULTI_ARRAY
#
#     contains
#
#       subroutine hoge( in,   &   ! Comment blocks between continuation lines are ignored.
#           &            out )
#         !
#         ! Comment blocks for the subroutines or functions
#         !
#         character(*),intent(in):: in ! Comment blocks for the arguments.
#         character(*),intent(out),allocatable,target  :: in
#                                      ! Comment blocks can be
#                                      ! written under Fortran statements.
#
#         character(32) :: file ! This comment parsed as a variable in below NAMELIST.
#         integer       :: id
#
#         namelist /varinfo_nml/ file, id
#                 !
#                 ! Comment blocks for the NAMELISTs.
#                 ! Information about variables are described above.
#                 !
#
#       ....
#
#       end subroutine hoge
#
#       integer function foo( in )
#         !
#         ! This part is considered as comment block.
#
#         ! Comment blocks under blank lines are ignored.
#         !
#         integer, intent(in):: inA ! This part is considered as comment block.
#
#                                   ! This part is ignored.
#
#       end function foo
#
#       subroutine hide( in,   &
#         &              out )      !:nodoc:
#         !
#         ! If "!:nodoc:" is described at end-of-line in subroutine
#         ! statement as above, the subroutine is ignored.
#         ! This assignment can be used to modules, subroutines,
#         ! functions, variables, constants, derived-types,
#         ! defined operators, defined assignments,
#         ! list of imported modules ("use" statement).
#         !
#
#       ....
#
#       end subroutine hide
#
#     end module hogehoge
#
#--
#
#== 今後の改良予定
#
#* モジュール本体に定義された NAMELIST を構文分析する.
#  * 定義されたところで出力するか, 入力される場所 (正規表現ならば
#    read\s*\(.*nml=<namelist>\s* で取得可能なはず)
#    で書き出すかはちょっと問題. たぶん書き出すところに
#    書いておくのが便利だと思う. (NAMELIST は公開要素に出来ないし)
#
#* NAMELIST もメソッド名として出力する.
#  * 1つの NAMELIST に対して出力先を 2 つ用意する
#    1. そのモジュールまたはプログラム本体に出力. 実際にどのサブルーチン
#       または関数で read されるのかはそれだけでは分からないため,
#       どこで read されるかは解析し, コメントとして出力する
#    2. "NAMELIST" というモジュールをその場で定義し, その中のメソッドとして
#       上記の情報を書き出す. この場合, 「どのモジュールまたはメイン
#       プログラム」で呼ばれるか分からないため, それに関しても
#       コメントに書き出す. 結果, 複数のファイル内において定義される
#       "NAMELIST" という "Classes" に解析した全ての NAMELIST が書き出される
#       ことになる.
#
#* 構造体, 変数, 定数の時にメソッドの横に出力される無駄な "( )" を消す.
#  html_generator に手を入れれば意外と簡単に処理できるはず.
#  * 具体案1
#    * :nobracket というシンボルを指定した場合は完全に空白のみ出力する.
#
#* ";" を改行と認識するようになっているが, 文字列として使われて
#  いる場合 (";" などとなる場合) を見分けるようにしておく.
#
#* 行末の & を継続行と認識するようになっているが, 文字列として使われて
#  いる場合 ("&  ! " などとなる場合) を見分けるようにしておく.
#
#* メインプログラム中の NAMELIST が private に入っているので見えないのは
#  不便なため, NAMELIST だけは見えるようにするなどの処置が必要
#
#* メインプログラムで下位の NAMELIST のリストを全部探査し, それらを
#  表示できるようにしておくと良いだろう.
#  * "== NAEMLIST" などと書いてある場合のみ, それらがコメント部に
#    現れるようにしておくのが良いと考えられる.
#
#++


require "rdoc/code_objects"

module RDoc

  class Token

    NO_TEXT = "??".freeze

    def initialize(line_no, char_no)
      @line_no = line_no
      @char_no = char_no
      @text    = NO_TEXT
    end
    # Because we're used in contexts that expect to return a token,
    # we set the text string and then return ourselves
    def set_text(text)
      @text = text
      self
    end

    attr_reader :line_no, :char_no, :text

  end

  # See rdoc/parsers/parse_f95.rb

  class Fortran95parser

    extend ParserFactory
    parse_files_matching(/\.((f|F)9(0|5)|F)$/)

    @@external_aliases = []
    @@public_methods   = []

    # "false":: Comments are below source code
    # "true" :: Comments are upper source code
    COMMENTS_ARE_UPPER  = false

    # Internal alias message
    INTERNAL_ALIAS_MES = "Alias for"

    # External alias message
    EXTERNAL_ALIAS_MES = "The entity is"

    # prepare to parse a Fortran 95 file
    def initialize(top_level, file_name, body, options, stats)
      @body = body
      @stats = stats
      @file_name  = file_name
      @options = options
      @top_level = top_level
      @progress = $stderr unless options.quiet
    end

    # devine code constructs
    def scan

      # remove private comment
      remaining_code = remove_private_comments(@body)

      # continuation lines are united to one line
      remaining_code = united_to_one_line(remaining_code)

      # collect comment for file entity
      whole_comment, remaining_code = collect_first_comment(remaining_code)
      @top_level.comment = whole_comment

      # "module" parts are parsed
      #
      while remaining_code =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$(.*?)^\s*?end\s+module.*?$/im
        remaining_code = $~.pre_match
        remaining_code << $~.post_match
        module_code = remove_empty_head_lines($&)
        module_name = $1
        f9x_trailing = find_comments($2)
        next if f9x_trailing =~ /^:nodoc:/
        progress "m"
        @stats.num_modules += 1
        f9x_module = @top_level.add_module NormalClass, module_name
        f9x_module.record_location @top_level

        f9x_comment = COMMENTS_ARE_UPPER ? 
          find_comments($~.pre_match)  + "\n" + f9x_trailing :
            f9x_trailing + "\n" + find_comments(module_code.sub(/^.*$\n/i, ''))
        f9x_module.comment = f9x_comment
        parse_program_or_module(f9x_module, module_code)

        TopLevel.all_files.each do |name, toplevel|
          if toplevel.include_includes?(module_name, @options.ignore_case)
            if !toplevel.include_requires?(@file_name, @options.ignore_case)
              toplevel.add_require(Require.new(@file_name, ""))
            end
          end
          toplevel.each_classmodule{|m|
            if m.include_includes?(module_name, @options.ignore_case)
              if !m.include_requires?(@file_name, @options.ignore_case)
                m.add_require(Require.new(@file_name, ""))
              end
            end
          }
        end
      end

      # "program" parts are parsed
      #
      # contains 以下の内部サブルーチンが存在するなど,
      # サブプログラムの集まりとは少々違うため.
      #
      while remaining_code =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$(.*?)^\s*?end\s+program.*?$/im
        remaining_code = $~.pre_match
        remaining_code << $~.post_match
        program_code = remove_empty_head_lines($&)
        progress "p"
        program_name = $1
        program_trailing = find_comments($2)
        program_comment = COMMENTS_ARE_UPPER ? 
          find_comments($~.pre_match) + "\n" + program_trailing : 
            program_trailing + "\n" + find_comments(program_code.sub(/^.*$\n/i, ''))
        program_comment = "\n\n= <i>Program</i> <tt>#{program_name}</tt>\n\n" \
                          + program_comment
        @top_level.comment << program_comment
        parse_program_or_module(@top_level, program_code, :private)
      end

      # External subprograms and functions are parsed
      #
      # 単一のファイル内において program や module に格納されない,
      # 外部サブルーチン, 外部関数部分の解析.
      #
      parse_program_or_module(@top_level, remaining_code, :public, true)

      @top_level
    end  # End of scan

    private

    def parse_program_or_module(container, code,
                                visibility=:public, external=nil)
      return unless container
      return unless code

      remaining_code = "#{code}"

      #
      # Parse variables before "contains" in module
      #
      # namelist 変数の定義に使われたり, これ自体が定数, 変数
      # 提供されるのに利用される. (変数や定数として利用される場合,
      # これもメソッドとして提供する.
      #
      before_contains_code = nil
      if remaining_code =~ /
                            ^\s*?module\s+\w+\s*?(!.*?)?$
                            (.*?)
                            ^\s*?contains\s*?(!.*?)?$
                            (.*?)
                            ^\s*?end\s+module.*?$
                           /imx
        before_contains_code = $2
      end
      if remaining_code =~ 
          /^\s*?module\s+(\w+)\s*?$(.*?)^\s*?end\s+module.*?$/im &&
          !before_contains_code
        before_contains_code = "#{remaining_code}"
      end
      if before_contains_code 
        before_contains_code.gsub!(/^\s*?interface\s+.*?\s+end\s+interface.*?$/im, "")
        before_contains_code.gsub!(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "")
      end

      #
      # Parse global "use"
      #
      use_check_code = "#{before_contains_code}"
      cascaded_modules_list = []
      while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i
        use_check_code = $~.pre_match
        use_check_code << $~.post_match
        used_mod_name = $1.strip.chomp
        used_list = $2 || ""
        used_trailing = $3 || ""
        next if used_trailing =~ /!:nodoc:/
        if !container.include_includes?(used_mod_name, @options.ignore_case)
          progress "."
          container.add_include Include.new(used_mod_name, "")
        end
        if ! (used_list =~ /\,\s*?only\s*?:/i )
          cascaded_modules_list << "\#" + used_mod_name
        end
      end

      #
      # Parse public and private, and store information.
      # This information is used when "add_method" and
      # "set_visibility_for" are called.
      #
      visibility_default, visibility_info = 
                parse_visibility(remaining_code, visibility, container)
      @@public_methods.concat visibility_info

      if visibility_default == :public
        if !cascaded_modules_list.empty?
          cascaded_modules = 
            Attr.new("Cascaded Modules",
                     "Imported modules all of whose components are published again",
                     "",
                     cascaded_modules_list.join(", "))
          container.add_attribute(cascaded_modules)
        end
      end

      #
      # Check rename elements
      #
      use_check_code = "#{before_contains_code}"
      while use_check_code =~ /^\s*?use\s+(\w+)\s*?\,(.+)$/i
        use_check_code = $~.pre_match
        use_check_code << $~.post_match
        used_mod_name = $1.strip.chomp
        used_elements = $2.sub(/\s*?only\s*?:\s*?/i, '')
        used_elements.split(",").each{ |used|
          if /\s*?(\w+)\s*?=>\s*?(\w+)\s*?/ =~ used
            local = $1
            org = $2
            @@public_methods.collect!{ |pub_meth|
              if local == pub_meth["name"] ||
                  local.upcase == pub_meth["name"].upcase &&
                  @options.ignore_case
                pub_meth["name"] = org
                pub_meth["local_name"] = local
              end
              pub_meth
            }
          end
        }
      end

      #
      # Parse private "use"
      #
      use_check_code = "#{remaining_code}"
      while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i
        use_check_code = $~.pre_match
        use_check_code << $~.post_match
        used_mod_name = $1.strip.chomp
        used_trailing = $3 || ""
        next if used_trailing =~ /!:nodoc:/
        if !container.include_includes?(used_mod_name, @options.ignore_case)
          progress "."
          container.add_include Include.new(used_mod_name, "")
        end
      end

      container.each_includes{ |inc|
        TopLevel.all_files.each do |name, toplevel|
          indicated_mod = toplevel.find_symbol(inc.name,
                                               nil, @options.ignore_case)
          if indicated_mod
            indicated_name = indicated_mod.parent.file_relative_name
            if !container.include_requires?(indicated_name, @options.ignore_case)
              container.add_require(Require.new(indicated_name, ""))
            end
            break
          end
        end
      }

      #
      # Parse derived-types definitions
      #
      derived_types_comment = ""
      while remaining_code =~ /^\s*?
                                    type[\s\,]+(public|private)?\s*?(::)?\s*?
                                    (\w+)\s*?(!.*?)?$
                                    (.*?)
                                    ^\s*?end\s+type.*?$
                              /imx
        remaining_code = $~.pre_match
        remaining_code << $~.post_match
        typename = $3.chomp.strip
        type_elements = $5 || ""
        type_code = remove_empty_head_lines($&)
        type_trailing = find_comments($4)
        next if type_trailing =~ /^:nodoc:/
        type_visibility = $1
        type_comment = COMMENTS_ARE_UPPER ? 
          find_comments($~.pre_match) + "\n" + type_trailing :
            type_trailing + "\n" + find_comments(type_code.sub(/^.*$\n/i, ''))
        type_element_visibility_public = true
        type_code.split("\n").each{ |line|
          if /^\s*?private\s*?$/ =~ line
            type_element_visibility_public = nil
            break
          end
        } if type_code

        args_comment = ""
        type_args_info = nil

        if @options.show_all
          args_comment = find_arguments(nil, type_code, true)
        else
          type_public_args_list = []
          type_args_info = definition_info(type_code)
          type_args_info.each{ |arg|
            arg_is_public = type_element_visibility_public
            arg_is_public = true if arg.include_attr?("public")
            arg_is_public = nil if arg.include_attr?("private")
            type_public_args_list << arg.varname if arg_is_public
          }
          args_comment = find_arguments(type_public_args_list, type_code)
        end

        type = AnyMethod.new("type #{typename}", typename)
        type.singleton = false
        type.params = ""
        type.comment = "<b><em> Derived Type </em></b> :: <tt></tt>\n"
        type.comment << args_comment if args_comment
        type.comment << type_comment if type_comment
        progress "t"
        @stats.num_methods += 1
        container.add_method type

        set_visibility(container, typename, visibility_default, @@public_methods)

        if type_visibility
          type_visibility.gsub!(/\s/,'')
          type_visibility.gsub!(/\,/,'')
          type_visibility.gsub!(/:/,'')
          type_visibility.downcase!
          if type_visibility == "public"
            container.set_visibility_for([typename], :public)
          elsif type_visibility == "private"
            container.set_visibility_for([typename], :private)
          end
        end

        check_public_methods(type, container.name)

        if @options.show_all
          derived_types_comment << ", " unless derived_types_comment.empty?
          derived_types_comment << typename
        else
          if type.visibility == :public
          derived_types_comment << ", " unless derived_types_comment.empty?
          derived_types_comment << typename
          end
        end

      end

      if !derived_types_comment.empty?
        derived_types_table = 
          Attr.new("Derived Types", "Derived_Types", "", 
                   derived_types_comment)
        container.add_attribute(derived_types_table)
      end

      #
      # move interface scope
      #
      interface_code = ""
      while remaining_code =~ /^\s*?
                                   interface(
                                              \s+\w+                      |
                                              \s+operator\s*?\(.*?\)       |
                                              \s+assignment\s*?\(\s*?=\s*?\)
                                            )?\s*?$
                                   (.*?)
                                   ^\s*?end\s+interface.*?$
                              /imx
        interface_code << remove_empty_head_lines($&) + "\n"
        remaining_code = $~.pre_match
        remaining_code << $~.post_match
      end

      #
      # Parse global constants or variables in modules
      #
      const_var_defs = definition_info(before_contains_code)
      const_var_defs.each{|defitem|
        next if defitem.nodoc
        const_or_var_type = "Variable"
        const_or_var_progress = "v"
        if defitem.include_attr?("parameter")
          const_or_var_type = "Constant"
          const_or_var_progress = "c"
        end
        const_or_var = AnyMethod.new(const_or_var_type, defitem.varname)
        const_or_var.singleton = false
        const_or_var.params = ""
        self_comment = find_arguments([defitem.varname], before_contains_code)
        const_or_var.comment = "<b><em>" + const_or_var_type + "</em></b> :: <tt></tt>\n"
        const_or_var.comment << self_comment if self_comment
        progress const_or_var_progress
        @stats.num_methods += 1
        container.add_method const_or_var

        set_visibility(container, defitem.varname, visibility_default, @@public_methods)

        if defitem.include_attr?("public")
          container.set_visibility_for([defitem.varname], :public)
        elsif defitem.include_attr?("private")
          container.set_visibility_for([defitem.varname], :private)
        end

        check_public_methods(const_or_var, container.name)


      } if const_var_defs

      # Parse subroutines
      #
      contains_code = ""
      while remaining_code =~ /^\s*?
                               (recursive|pure|elemental)?\s*?
                               subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
                               (.*?)
                               ^(\s*?end\s+subroutine.*?)$
                              /imx
        remaining_code = $~.pre_match
        remaining_code << $~.post_match
        post_code = "#{$~.post_match}"
        pre_code = "#{$~.pre_match}"
        subname = $2.chomp.strip
        params = $3 || ""
        subroutine_code = remove_empty_head_lines($&)
        subroutine_end = $6
        subroutine_prefix = $1 || ""
        subroutine_trailing = $4 || "!"
        next if subroutine_trailing =~ /!:nodoc:/

        #
        # If the subroutine contains internal sub procedures,
        # they are parsed as private methods.
        #
        if subroutine_code =~ /^\s*?contains\s*?(!.*?)?$/
          before_contains = $~.pre_match
          body_contains   = $&
          after_contains  = $~.post_match
          while after_contains =~ \
                              /(.*?)
                               ^\s*?([\w\s\(\)]+\s+)?
                               subroutine\s+.*?(!.*?)?$
                               (.*?)
                               ^(\s*end\s+subroutine.*?)$
                              /imx
            contains_code << $&
            contains_code << "\n\n"
            after_contains = $~.pre_match
            after_contains << $~.post_match
          end
          while after_contains =~ \
                              /(.*?)
                               ^\s*?([\w\s\(\)]+\s+)?
                               function\s+.*?(!.*?)?$
                               (.*?)
                               ^(\s*end\s+function.*?)$
                              /imx
            contains_code << $&
            contains_code << "\n\n"
            after_contains = $~.pre_match
            after_contains << $~.post_match
          end

          if after_contains =~ /^(\s*end\s+subroutine.*?)$/i
            subroutine_end = $&
            subroutine_code = subroutine_code.sub(/^(\s*?end\s+subroutine.*?)$/, '') +
              "\n" + contains_code + "\n" + subroutine_end
            parse_program_or_module(container, contains_code, :private)
            contains_code = ""
          else
            remaining_code = "#{pre_code}"
            remaining_code << before_contains
            remaining_code << body_contains
            remaining_code << after_contains
            remaining_code << "#{post_code}"

            next # To start of subroutine parse loop

          end
        end    # End of treatment of after "contains"

        #
        # AnyMethod object for subroutine is created
        #
        subroutine_comment = COMMENTS_ARE_UPPER ? 
          "#{pre_code}" + "\n" + subroutine_trailing : 
            subroutine_trailing + "\n" + subroutine_code.sub(/^.*$\n/i, '')
        subroutine = AnyMethod.new("subroutine", subname)
        parse_subprogram(subroutine, params, 
                         subroutine_comment, subroutine_code,
                         before_contains_code, nil, subroutine_prefix)
        progress "s"
        @stats.num_methods += 1
        container.add_method subroutine

        set_visibility(container, subname, visibility_default, @@public_methods)
        check_external_aliases(subname, subroutine.params, subroutine.comment) if external

        check_public_methods(subroutine, container.name)

      end

      # Parse functions
      #
      contains_code = ""
      while remaining_code =~ /^\s*?
                    (recursive|pure|elemental)?\s*?
                    (
                        character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
                      | type\s*?\([\w\s]+?\)\s+
                      | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
                      | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
                      | double\s+precision\s+
                      | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
                      | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
                    )?
                    function\s+(\w+)\s*?
                    (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
                    (.*?)
                    ^(\s*?end\s+function.*?)$
                   /imx

        remaining_code = $~.pre_match
        remaining_code << $~.post_match
        post_code = "#{$~.post_match}"
        pre_code = "#{$~.pre_match}"
        function_prefix = $1 || ""
        function_type = $2 ? $2.chomp.strip : nil
        subname = $8.chomp.strip
        params_org = $9 ? $9 : ""
        function_code_org = remove_empty_head_lines($&)
        function_result_arg = $11 ? $11.chomp.strip : subname
        function_end = $14
        function_trailing = $12 || "!"
        next if function_trailing =~ /!:nodoc:/

        #
        # If the function contains internal sub procedures,
        # they are parsed as private methods.
        #
        if function_code_org =~ /^\s*?contains\s*?(!.*?)?$/
          before_contains = $~.pre_match
          body_contains   = $&
          after_contains  = $~.post_match
          while after_contains =~ \
                              /(.*?)
                               ^\s*?([\w\s\(\)]+\s+)?
                               subroutine\s+.*?(!.*?)?$
                               (.*?)
                               ^(\s*end\s+subroutine.*?)$
                              /imx
            contains_code << $&
            contains_code << "\n\n"
            after_contains = $~.pre_match
            after_contains << $~.post_match
          end
          while after_contains =~ \
                              /(.*?)
                               ^\s*?([\w\s\(\)]+\s+)?
                               function\s+.*?(!.*?)?$
                               (.*?)
                               ^(\s*end\s+function.*?)$
                              /imx
            contains_code << $&
            contains_code << "\n\n"
            after_contains = $~.pre_match
            after_contains << $~.post_match
          end
          if after_contains =~ /^(\s*end\s+function.*?)$/i
            function_end = $&
            function_code_org = function_code_org.sub(/^(\s*?end\s+function.*?)$/, '') +
              "\n" + contains_code + "\n" + function_end
            parse_program_or_module(container, contains_code, :private)
            contains_code = ""
          else
            remaining_code = "#{pre_code}"
            remaining_code << before_contains
            remaining_code << body_contains
            remaining_code << after_contains
            remaining_code << "#{post_code}"

            next # To start of function parse loop

          end
        end    # End of treatment of after "contains"

        #
        # AnyMethod object for function is created
        #
        function_comment = COMMENTS_ARE_UPPER ? 
          "#{pre_code}" + "\n" + function_trailing : 
            function_trailing + "\n " + function_code_org.sub(/^.*$\n/i, '')

        function_code = "#{function_code_org}"
        if function_type
          function_code << "\n" + function_type + " :: " + function_result_arg
        end

        params = params_org.sub(/^\(/, "\(#{function_result_arg}, ")

        function = AnyMethod.new("function", subname)
        parse_subprogram(function, params, 
                         function_comment, function_code, 
                         before_contains_code, true, function_prefix)

        # Specific modification due to function
        function.params.sub!(/\(\s*?#{function_result_arg}\s*?,\s*?/, "\( ")
        function.params << " result(" + function_result_arg + ")"
        function.start_collecting_tokens
        function.add_token Token.new(1,1).set_text(function_code_org)

        progress "f"
        @stats.num_methods += 1
        container.add_method function

        set_visibility(container, subname, visibility_default, @@public_methods)

        check_external_aliases(subname, function.params, function.comment) if external

        check_public_methods(function, container.name)
      end

      #
      # Parse interface
      #
      interface_scope = false
      generic_name = ""
      interface_code.split("\n").each{ |line|
        if /^\s*?
                 interface(
                            \s+\w+|
                            \s+operator\s*?\(.*?\)|
                            \s+assignment\s*?\(\s*?=\s*?\)
                          )?
                 \s*?(!.*?)?$
           /ix =~ line
          generic_name = $1 ? $1.strip.chomp : nil
          interface_trailing = $2 || "!"
          interface_scope = true
          interface_scope = false if interface_trailing =~ /!:nodoc:/
#          if generic_name =~ /operator\s*?\((.*?)\)/i
#            operator_name = $1
#            if operator_name && !operator_name.empty?
#              generic_name = "#{operator_name}"
#            end
#          end
#          if generic_name =~ /assignment\s*?\((.*?)\)/i
#            assignment_name = $1
#            if assignment_name && !assignment_name.empty?
#              generic_name = "#{assignment_name}"
#            end
#          end
        end
        if /^\s*?end\s+interface/i =~ line
          interface_scope = false
          generic_name = nil
        end
        # internal alias
        if interface_scope && /^\s*?module\s+procedure\s+(.*?)(!.*?)?$/i =~ line
          procedures = $1.strip.chomp
          procedures_trailing = $2 || "!"
          next if procedures_trailing =~ /!:nodoc:/
          procedures.split(",").each{ |proc|
            proc.strip!
            proc.chomp!
            next if generic_name == proc || !generic_name
            old_meth = container.find_symbol(proc, nil, @options.ignore_case)
            next if !old_meth
            nolink = old_meth.visibility == :private ? true : nil
            nolink = nil if @options.show_all
            new_meth = 
               initialize_external_method(generic_name, proc, 
                                          old_meth.params, nil, 
                                          old_meth.comment, 
                                          old_meth.clone.token_stream[0].text, 
                                          true, nolink)
            new_meth.singleton = old_meth.singleton

            progress "i"
            @stats.num_methods += 1
            container.add_method new_meth

            set_visibility(container, generic_name, visibility_default, @@public_methods)

            check_public_methods(new_meth, container.name)

          }
        end

        # external aliases
        if interface_scope && 
            /^\s*?
                  ([\w\s\(\)]+\s+)?
                  (subroutine|function)\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
            /ix =~ line
          proc = $3.chomp.strip
          generic_name = proc unless generic_name
          params = $4 ? $4 : ""
          procedures_trailing = $5 || "!"
          next if procedures_trailing =~ /!:nodoc:/
          indicated_method = nil
          indicated_file   = nil
          TopLevel.all_files.each do |name, toplevel|
            indicated_method = toplevel.find_local_symbol(proc, @options.ignore_case)
            indicated_file = name
            break if indicated_method
          end

          if indicated_method
            external_method = 
              initialize_external_method(generic_name, proc, 
                                         indicated_method.params, 
                                         indicated_file, 
                                         indicated_method.comment)

            progress "e"
            @stats.num_methods += 1
            container.add_method external_method
            set_visibility(container, generic_name, visibility_default, @@public_methods)
            if !container.include_requires?(indicated_file, @options.ignore_case)
              container.add_require(Require.new(indicated_file, ""))
            end
            check_public_methods(external_method, container.name)

          else
            @@external_aliases << {
              "new_name"  => generic_name,
              "old_name"  => proc,
              "file_or_module" => container,
              "visibility" => find_visibility(container, generic_name, @@public_methods) || visibility_default
            }
          end
        end

      } if interface_code # End of interface_code.split("\n").each ...

      #
      # Already imported methods are removed from @@public_methods.
      # Remainders are assumed to be imported from other modules.
      #
      # 既に参照済みのメソッドは @@public_methods から取り除く.
      # 残りは外部モジュールからの参照と仮定する.
      #
      @@public_methods.delete_if{ |method| method["entity_is_discovered"]}

      @@public_methods.each{ |pub_meth|
        next unless pub_meth["file_or_module"].name == container.name
        pub_meth["used_modules"].each{ |used_mod|
          TopLevel.all_classes_and_modules.each{ |modules|
            if modules.name == used_mod ||
                modules.name.upcase == used_mod.upcase &&
                @options.ignore_case
              modules.method_list.each{ |meth|
                if meth.name == pub_meth["name"] ||
                    meth.name.upcase == pub_meth["name"].upcase &&
                    @options.ignore_case
                  new_meth = initialize_public_method(meth,
                                                      modules.name)
                  if pub_meth["local_name"]
                    new_meth.name = pub_meth["local_name"]
                  end
                  progress "e"
                  @stats.num_methods += 1
                  container.add_method new_meth
                end
              }
            end
          }
        }
      }

      container
    end  # End of parse_program_or_module

    #
    # Parse arguments, comment, code of subroutine and function.
    # Return AnyMethod object.
    #
    def parse_subprogram(subprogram, params, comment, code, 
                         before_contains=nil, function=nil, prefix=nil)
      subprogram.singleton = false
      prefix = "" if !prefix
      arguments = params.sub(/\(/, "").sub(/\)/, "").split(",") if params
      args_comment, params_opt = 
        find_arguments(arguments, code.sub(/^s*?contains\s*?(!.*?)?$.*/im, ""),
                       nil, nil, true)
      params_opt = "( " + params_opt + " ) " if params_opt
      subprogram.params = params_opt || ""
      namelist_comment = find_namelists(code, before_contains)

      block_comment = find_comments comment
      if function
        subprogram.comment = "<b><em> Function </em></b> :: <em>#{prefix}</em>\n"
      else
        subprogram.comment = "<b><em> Subroutine </em></b> :: <em>#{prefix}</em>\n"
      end
      subprogram.comment << args_comment if args_comment
      subprogram.comment << block_comment if block_comment
      subprogram.comment << namelist_comment if namelist_comment

      # For output source code
      subprogram.start_collecting_tokens
      subprogram.add_token Token.new(1,1).set_text(code)

      subprogram
    end

    #
    # Collect comment for file entity
    #
    def collect_first_comment(body)
      comment = ""
      not_comment = ""
      comment_start = false
      comment_end   = false
      body.split("\n").each{ |line|
        if comment_end
          not_comment << line
          not_comment << "\n"
        elsif /^\s*?!\s?(.*)$/i =~ line
          comment_start = true
          comment << $1
          comment << "\n"
        elsif /^\s*?$/i =~ line
          comment_end = true if comment_start && COMMENTS_ARE_UPPER
        else
          comment_end = true
          not_comment << line
          not_comment << "\n"
        end
      }
      return comment, not_comment
    end


    # Return comments of definitions of arguments
    #
    # If "all" argument is true, information of all arguments are returned.
    # If "modified_params" is true, list of arguments are decorated,
    # for exameple, optional arguments are parenthetic as "[arg]".
    #
    def find_arguments(args, text, all=nil, indent=nil, modified_params=nil)
      return unless args || all
      indent = "" unless indent
      args = ["all"] if all
      params = "" if modified_params
      comma = ""
      return unless text
      args_rdocforms = "\n"
      remaining_lines = "#{text}"
      definitions = definition_info(remaining_lines)
      args.each{ |arg|
        arg.strip!
        arg.chomp!
        definitions.each { |defitem|
          if arg == defitem.varname.strip.chomp || all
            args_rdocforms << <<-"EOF"

#{indent}<tt><b>#{defitem.varname.chomp.strip}#{defitem.arraysuffix}</b> #{defitem.inivalue}</tt> :: 
#{indent}   <tt>#{defitem.types.chomp.strip}</tt>
EOF
            if !defitem.comment.chomp.strip.empty?
              comment = ""
              defitem.comment.split("\n").each{ |line|
                comment << "       " + line + "\n"
              }
              args_rdocforms << <<-"EOF"

#{indent}   <tt></tt> :: 
#{indent}       <tt></tt>
#{indent}       #{comment.chomp.strip}
EOF
            end

            if modified_params
              if defitem.include_attr?("optional")
                params << "#{comma}[#{arg}]"
              else
                params << "#{comma}#{arg}"
              end
              comma = ", "
            end
          end
        }
      }
      if modified_params
        return args_rdocforms, params
      else
        return args_rdocforms
      end
    end

    # Return comments of definitions of namelists
    #
    def find_namelists(text, before_contains=nil)
      return nil if !text
      result = ""
      lines = "#{text}"
      before_contains = "" if !before_contains
      while lines =~ /^\s*?namelist\s+\/\s*?(\w+)\s*?\/([\s\w\,]+)$/i
        lines = $~.post_match
        nml_comment = COMMENTS_ARE_UPPER ? 
            find_comments($~.pre_match) : find_comments($~.post_match)
        nml_name = $1
        nml_args = $2.split(",")
        result << "\n\n=== NAMELIST <tt><b>" + nml_name + "</tt></b>\n\n"
        result << nml_comment + "\n" if nml_comment
        if lines.split("\n")[0] =~ /^\//i
          lines = "namelist " + lines
        end
        result << find_arguments(nml_args, "#{text}" + "\n" + before_contains)
      end
      return result
    end

    #
    # Comments just after module or subprogram, or arguments are
    # returnd. If "COMMENTS_ARE_UPPER" is true, comments just before
    # modules or subprograms are returnd
    #
    def find_comments text
      return "" unless text
      lines = text.split("\n")
      lines.reverse! if COMMENTS_ARE_UPPER
      comment_block = Array.new
      lines.each do |line|
        break if line =~ /^\s*?\w/ || line =~ /^\s*?$/
        if COMMENTS_ARE_UPPER
          comment_block.unshift line.sub(/^\s*?!\s?/,"")
        else
          comment_block.push line.sub(/^\s*?!\s?/,"")
        end
      end
      nice_lines = comment_block.join("\n").split "\n\s*?\n"
      nice_lines[0] ||= ""
      nice_lines.shift
    end

    def progress(char)
      unless @options.quiet
        @progress.print(char)
        @progress.flush
      end
    end

    #
    # Create method for internal alias
    #
    def initialize_public_method(method, parent)
      return if !method || !parent

      new_meth = AnyMethod.new("External Alias for module", method.name)
      new_meth.singleton    = method.singleton
      new_meth.params       = method.params.clone
      new_meth.comment      = remove_trailing_alias(method.comment.clone)
      new_meth.comment      << "\n\n#{EXTERNAL_ALIAS_MES} #{parent.strip.chomp}\##{method.name}"

      return new_meth
    end

    #
    # Create method for external alias
    #
    # If argument "internal" is true, file is ignored.
    #
    def initialize_external_method(new, old, params, file, comment, token=nil,
                                   internal=nil, nolink=nil)
      return nil unless new || old

      if internal
        external_alias_header = "#{INTERNAL_ALIAS_MES} "
        external_alias_text   = external_alias_header + old 
      elsif file
        external_alias_header = "#{EXTERNAL_ALIAS_MES} "
        external_alias_text   = external_alias_header + file + "#" + old
      else
        return nil
      end
      external_meth = AnyMethod.new(external_alias_text, new)
      external_meth.singleton    = false
      external_meth.params       = params
      external_comment = remove_trailing_alias(comment) + "\n\n" if comment
      external_meth.comment = external_comment || ""
      if nolink && token
        external_meth.start_collecting_tokens
        external_meth.add_token Token.new(1,1).set_text(token)
      else
        external_meth.comment << external_alias_text
      end

      return external_meth
    end



    #
    # Parse visibility
    #
    def parse_visibility(code, default, container)
      result = []
      visibility_default = default || :public

      used_modules = []
      container.includes.each{|i| used_modules << i.name} if container

      remaining_code = code.gsub(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "")
      remaining_code.split("\n").each{ |line|
        if /^\s*?private\s*?$/ =~ line
          visibility_default = :private
          break
        end
      } if remaining_code

      remaining_code.split("\n").each{ |line|
        if /^\s*?private\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line
          methods = $2.sub(/!.*$/, '')
          methods.split(",").each{ |meth|
            meth.sub!(/!.*$/, '')
            result << {
              "name" => meth.chomp.strip,
              "visibility" => :private,
              "used_modules" => used_modules.clone,
              "file_or_module" => container,
              "entity_is_discovered" => nil,
              "local_name" => nil
            }
          }
        elsif /^\s*?public\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line
          methods = $2.sub(/!.*$/, '')
          methods.split(",").each{ |meth|
            meth.sub!(/!.*$/, '')
            result << {
              "name" => meth.chomp.strip,
              "visibility" => :public,
              "used_modules" => used_modules.clone,
              "file_or_module" => container,
              "entity_is_discovered" => nil,
              "local_name" => nil
            }
          }
        end
      } if remaining_code

      if container
        result.each{ |vis_info|
          vis_info["parent"] = container.name
        }
      end

      return visibility_default, result
    end

    #
    # Set visibility
    #
    # "subname" element of "visibility_info" is deleted.
    #
    def set_visibility(container, subname, visibility_default, visibility_info)
      return unless container || subname || visibility_default || visibility_info
      not_found = true
      visibility_info.collect!{ |info|
        if info["name"] == subname ||
            @options.ignore_case && info["name"].upcase == subname.upcase
          if info["file_or_module"].name == container.name
            container.set_visibility_for([subname], info["visibility"])
            info["entity_is_discovered"] = true
            not_found = false
          end
        end
        info
      }
      if not_found
        return container.set_visibility_for([subname], visibility_default)
      else
        return container
      end
    end

    #
    # Find visibility
    #
    def find_visibility(container, subname, visibility_info)
      return nil if !subname || !visibility_info
      visibility_info.each{ |info|
        if info["name"] == subname ||
            @options.ignore_case && info["name"].upcase == subname.upcase
          if info["parent"] == container.name
            return info["visibility"]
          end
        end
      }
      return nil
    end

    #
    # Check external aliases
    #
    # subname というサブルーチン名, または関数名を持つファイルを
    # 探査し, 存在する場合にはそのファイル内へメソッドを追加する.
    #
    def check_external_aliases(subname, params, comment)
      @@external_aliases.each{ |alias_item|
        if subname == alias_item["old_name"] ||
                    subname.upcase == alias_item["old_name"].upcase &&
                            @options.ignore_case
          progress "e"
          @stats.num_methods += 1
          alias_item["file_or_module"].add_method( \
                initialize_external_method(alias_item["new_name"], 
                                           subname, params, @file_name, 
                                           comment) )
          alias_item["file_or_module"].set_visibility_for([alias_item["new_name"]], alias_item["visibility"])

          if !alias_item["file_or_module"].include_requires?(@file_name, @options.ignore_case)
            alias_item["file_or_module"].add_require(Require.new(@file_name, ""))
          end

        end
      }
    end

    #
    # Check public_methods
    #
    # use したモジュールからそのまま引き継いで public として
    # 公開する場合のチェックを行う. 
    # subname というサブルーチン名, または関数名を持つファイルを
    # 探査し, 存在する場合にはそのファイル内へメソッドを追加する.
    #
    def check_public_methods(method, parent)
      return if !method || !parent
      @@public_methods.each{ |alias_item|
        parent_is_used_module = nil
        alias_item["used_modules"].each{ |used_module|
          if used_module == parent ||
              used_module.upcase == parent.upcase &&
              @options.ignore_case
            parent_is_used_module = true
          end
        }
        next if !parent_is_used_module

        if method.name == alias_item["name"] ||
            method.name.upcase == alias_item["name"].upcase &&
            @options.ignore_case

          new_meth = initialize_public_method(method, parent)
          if alias_item["local_name"]
            new_meth.name = alias_item["local_name"]
          end

          progress "e"
          @stats.num_methods += 1
          alias_item["file_or_module"].add_method new_meth
        end
      }
    end


    #
    # Continuous lines are united.
    #
    # Comments in continuous lines are removed.
    #
    def united_to_one_line(f90src)
      lines = f90src.split("\n")
      body = ""
      continuing = false
      lines.each{ |line|
        if /^\s*?&(.*)&?/ =~ line
          body << $1.sub(/&\s*?(!.*)?$/, '')
          continuing = continuous_line?(line)
        elsif continuing
          if /^\s*?$/ =~ line || /^\s*?!/ =~ line
            body << ""
          else
            body << line.sub(/&\s*?(!.*)?$/, '')
            continuing = continuous_line?(line)
          end
        else
          body << "\n" + line.sub(/&\s*?(!.*)?$/, '')
          continuing = continuous_line?(line)
        end
      }
      return body.gsub(/&/, '').gsub(/;/, "\n")
    end

    #
    # Continuous line checker
    #
    def continuous_line?(line)
      continuous = false
      if /&\s*?(!.*)?$/ =~ line
        continuous = true
        if comment_out?($~.pre_match)
          continuous = false
        end
      end
      return continuous
    end

    #
    # Comment out checker
    #
    def comment_out?(line)
      commentout = false
      squote = false ; dquote = false
      line.split("").each { |char|
        if !(squote) && !(dquote)
          case char
          when "!" ; commentout = true ; break
          when "\""; dquote = true
          when "\'"; squote = true
          else next
          end
        elsif squote
          case char
          when "\'"; squote = false
          else next
          end
        elsif dquote
          case char
          when "\""; dquote = false
          else next
          end
        end
      }
      return commentout
    end

    #
    # Remove "Alias for" in end of comments
    #
    def remove_trailing_alias(text)
      return "" if !text
      lines = text.split("\n").reverse
      comment_block = Array.new
      checked = false
      lines.each do |line|
        if !checked 
          if /^\s?#{INTERNAL_ALIAS_MES}/ =~ line ||
              /^\s?#{EXTERNAL_ALIAS_MES}/ =~ line
            checked = true
            next
          end
        end
        comment_block.unshift line
      end
      nice_lines = comment_block.join("\n")
      nice_lines ||= ""
      return nice_lines
    end

    # Empty lines in header are removed
    def remove_empty_head_lines(text)
      return "" unless text
      lines = text.split("\n")
      header = true
      lines.delete_if{ |line|
        header = false if /\S/ =~ line
        header && /^\s*?$/ =~ line
      }
      lines.join("\n")
    end


    # header marker "=", "==", ... are removed
    def remove_header_marker(text)
      return text.gsub(/^\s?(=+)/, '<tt></tt>\1')
    end

    def remove_private_comments(body)
      body.gsub!(/^!--\s*?$.*?^!\+\+\s*?$/m, '')
      return body
    end


    #
    # Information of arguments of subroutines and functions in Fortran95
    #
    class Fortran95Definition

      # Name of variable
      #
      # 変数名
      attr_reader   :varname

      # Types of variable
      #
      # 型情報
      attr_reader   :types

      # Initial Value
      #
      # 初期値
      attr_reader   :inivalue

      # Suffix of array
      #
      # 配列接尾詞
      attr_reader   :arraysuffix

      # Comments
      #
      # 行の末尾にかかれるコメント
      attr_accessor   :comment

      # Flag of non documentation
      #
      # ドキュメント出力しないフラグ
      attr_accessor   :nodoc

      def initialize(varname, types, inivalue, arraysuffix, comment,
                     nodoc=false)
        @varname = varname
        @types = types
        @inivalue = inivalue
        @arraysuffix = arraysuffix
        @comment = comment
        @nodoc = nodoc
      end

      def to_s
        return <<-EOF
<Fortran95Definition: 
  varname=#{@varname}, types=#{types},
  inivalue=#{@inivalue}, arraysuffix=#{@arraysuffix}, nodoc=#{@nodoc}, 
  comment=
#{@comment}
>
EOF
      end

      #
      # If attr is included, true is returned
      #
      def include_attr?(attr)
        return if !attr
        @types.split(",").each{ |type|
          return true if type.strip.chomp.upcase == attr.strip.chomp.upcase
        }
        return nil
      end

    end # End of Fortran95Definition

    #
    # Parse string argument "text", and Return Array of
    # Fortran95Definition object
    #
    def definition_info(text)
      return nil unless text
      lines = "#{text}"
      defs = Array.new
      comment = ""
      trailing_comment = ""
      under_comment_valid = false
      lines.split("\n").each{ |line|
        if /^\s*?!\s?(.*)/ =~ line
          if COMMENTS_ARE_UPPER
            comment << remove_header_marker($1)
            comment << "\n"
          elsif defs[-1] && under_comment_valid
            defs[-1].comment << "\n"
            defs[-1].comment << remove_header_marker($1)
          end
          next
        elsif /^\s*?$/ =~ line
          comment = ""
          under_comment_valid = false
          next
        end
        type = ""
        characters = ""
        if line =~ /^\s*?
                    (
                        character\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
                      | type\s*?\([\w\s]+?\)[\s\,]*
                      | integer\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
                      | real\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
                      | double\s+precision[\s\,]*
                      | logical\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
                      | complex\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
                    )
                    (.*?::)?
                    (.+)$
                   /ix
          characters = $8
          type = $1
          type << $7.gsub(/::/, '').gsub(/^\s*?\,/, '') if $7
        else
          under_comment_valid = false
          next
        end
        squote = false ; dquote = false ; bracket = 0
        iniflag = false; commentflag = false
        varname = "" ; arraysuffix = "" ; inivalue = ""
        start_pos = defs.size
        characters.split("").each { |char|
          if !(squote) && !(dquote) && bracket <= 0 && !(iniflag) && !(commentflag)
            case char
            when "!" ; commentflag = true
            when "(" ; bracket += 1       ; arraysuffix = char
            when "\""; dquote = true
            when "\'"; squote = true
            when "=" ; iniflag = true     ; inivalue << char
            when ","
              defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
              varname = "" ; arraysuffix = "" ; inivalue = ""
              under_comment_valid = true
            when " " ; next
            else     ; varname << char
            end
          elsif commentflag
            comment << remove_header_marker(char)
            trailing_comment << remove_header_marker(char)
          elsif iniflag
            if dquote
              case char
              when "\"" ; dquote = false ; inivalue << char
              else      ; inivalue << char
              end
            elsif squote
              case char
              when "\'" ; squote = false ; inivalue << char
              else      ; inivalue << char
              end
            elsif bracket > 0
              case char
              when "(" ; bracket += 1 ; inivalue << char
              when ")" ; bracket -= 1 ; inivalue << char
              else     ; inivalue << char
              end
            else
              case char
              when ","
                defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
                varname = "" ; arraysuffix = "" ; inivalue = ""
                iniflag = false
                under_comment_valid = true
              when "(" ; bracket += 1 ; inivalue << char
              when "\""; dquote = true  ; inivalue << char
              when "\'"; squote = true  ; inivalue << char
              when "!" ; commentflag = true
              else     ; inivalue << char
              end
            end
          elsif !(squote) && !(dquote) && bracket > 0
            case char
            when "(" ; bracket += 1 ; arraysuffix << char
            when ")" ; bracket -= 1 ; arraysuffix << char
            else     ; arraysuffix << char
            end
          elsif squote
            case char
            when "\'"; squote = false ; inivalue << char
            else     ; inivalue << char
            end
          elsif dquote
            case char
            when "\""; dquote = false ; inivalue << char
            else     ; inivalue << char
            end
          end
        }
        defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
        if trailing_comment =~ /^:nodoc:/
          defs[start_pos..-1].collect!{ |defitem|
            defitem.nodoc = true
          }
        end
        varname = "" ; arraysuffix = "" ; inivalue = ""
        comment = ""
        under_comment_valid = true
        trailing_comment = ""
      }
      return defs
    end


  end # class Fortran95parser

end # module RDoc
