parse_f95.rb   [plain text]


# Parse a Fortran 95 file.

require "rdoc/code_objects"

module RDoc

  # See rdoc/parsers/parse_f95.rb
 
  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

  class Fortran95parser

    extend ParserFactory
    parse_files_matching(/\.(f9(0|5)|F)$/)
    
    # prepare to parse a Fortran 95 file
    def initialize(top_level, file_name, body, options, stats)
      @body = body
      @stats = stats
      @options = options
      @top_level = top_level
      @progress = $stderr unless options.quiet
    end
    
    # devine code constructs
    def scan

      # modules and programs
      if @body =~ /^(module|program)\s+(\w+)/i
	progress "m"
	f9x_module = @top_level.add_module NormalClass, $2
	f9x_module.record_location @top_level
	first_comment, second_comment = $`.gsub(/^!\s?/,"").split "\n\s*\n"
	if second_comment
	  @top_level.comment = first_comment if first_comment
	  f9x_module.comment = second_comment
	else
	  f9x_module.comment = first_comment if first_comment
	end
      end

      # use modules
      remaining_code = @body
      while remaining_code =~ /^\s*use\s+(\w+)/i
	remaining_code = $~.post_match
	progress "."
	f9x_module.add_include Include.new($1, "") if f9x_module
      end

      # subroutines
      remaining_code = @body
      while remaining_code =~ /^\s*subroutine\s+(\w+)\s*\((.*?)\)/im
	remaining_code = $~.post_match
        subroutine = AnyMethod.new("Text", $1)
	subroutine.singleton = false

        prematchText = $~.pre_match
        params = $2
        params.gsub!(/&/,'')
	subroutine.params = params
        comment = find_comments prematchText
	subroutine.comment = comment if comment

        subroutine.start_collecting_tokens
        remaining_code =~ /^\s*end\s+subroutine/i
        code = "subroutine #{subroutine.name} (#{subroutine.params})\n"
        code += $~.pre_match
        code += "\nend subroutine\n"
        subroutine.add_token Token.new(1,1).set_text(code)
        
        progress "s"
        f9x_module.add_method subroutine if f9x_module
      end

      @top_level

    end

    def find_comments text
      lines = text.split("\n").reverse
      comment_block = Array.new
      lines.each do |line|
	break if line =~ /^\s*\w/
        comment_block.unshift line.sub(/^!\s?/,"")
      end
      nice_lines = comment_block.join("\n").split "\n\s*\n"
      nice_lines.shift
      nice_lines.shift
      nice_lines.shift
    end

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

  end # class Fortran95parser

end # module RDoc