1 #= parse_f95.rb - Fortran95 Parser
5 #"parse_f95.rb" parses Fortran95 files with suffixes "f90", "F90", "f95"
6 #and "F95". Fortran95 files are expected to be conformed to Fortran95
11 #Fundamental rules are same as that of the Ruby parser.
12 #But comment markers are '!' not '#'.
14 #=== Correspondence between RDoc documentation and Fortran95 programs
16 #"parse_f95.rb" parses main programs, modules, subroutines, functions,
17 #derived-types, public variables, public constants,
18 #defined operators and defined assignments.
19 #These components are described in items of RDoc documentation, as follows.
21 #Files :: Files (same as Ruby)
23 #Methods :: Subroutines, functions, variables, constants, derived-types, defined operators, defined assignments
24 #Required files :: Files in which imported modules, external subroutines and external functions are defined.
25 #Included Modules :: List of imported modules
26 #Attributes :: List of derived-types, List of imported modules all of whose components are published again
28 #Components listed in 'Methods' (subroutines, functions, ...)
29 #defined in modules are described in the item of 'Classes'.
30 #On the other hand, components defined in main programs or
31 #as external procedures are described in the item of 'Files'.
33 #=== Components parsed by default
35 #By default, documentation on public components (subroutines, functions,
36 #variables, constants, derived-types, defined operators,
37 #defined assignments) are generated.
38 #With "--all" option, documentation on all components
39 #are generated (almost same as the Ruby parser).
41 #=== Information parsed automatically
43 #The following information is automatically parsed.
46 #* Types of variables and constants
47 #* Types of variables in the derived types, and initial values
48 #* NAMELISTs and types of variables in them, and initial values
50 #Aliases by interface statement are described in the item of 'Methods'.
52 #Components which are imported from other modules and published again
53 #are described in the item of 'Methods'.
55 #=== Format of comment blocks
57 #Comment blocks should be written as follows.
58 #Comment blocks are considered to be ended when the line without '!'
60 #The indentation is not necessary.
64 # ! Comment blocks for the files.
67 # ! The comment described in the part enclosed by
68 # ! "!--" and "!++" is ignored.
73 # ! Comment blocks for the modules (or the programs).
78 # logical :: a ! a private variable
79 # real, public :: b ! a public variable
80 # integer, parameter :: c = 0 ! a public constant
83 # public :: MULTI_ARRAY
88 # ! Comment blocks for the derived-types.
90 # real, pointer :: var(:) =>null() ! Comments block for the variables.
92 # end type MULTI_ARRAY
96 # subroutine hoge( in, & ! Comment blocks between continuation lines are ignored.
99 # ! Comment blocks for the subroutines or functions
101 # character(*),intent(in):: in ! Comment blocks for the arguments.
102 # character(*),intent(out),allocatable,target :: in
103 # ! Comment blocks can be
104 # ! written under Fortran statements.
106 # character(32) :: file ! This comment parsed as a variable in below NAMELIST.
109 # namelist /varinfo_nml/ file, id
111 # ! Comment blocks for the NAMELISTs.
112 # ! Information about variables are described above.
117 # end subroutine hoge
119 # integer function foo( in )
121 # ! This part is considered as comment block.
123 # ! Comment blocks under blank lines are ignored.
125 # integer, intent(in):: inA ! This part is considered as comment block.
127 # ! This part is ignored.
131 # subroutine hide( in, &
134 # ! If "!:nodoc:" is described at end-of-line in subroutine
135 # ! statement as above, the subroutine is ignored.
136 # ! This assignment can be used to modules, subroutines,
137 # ! functions, variables, constants, derived-types,
138 # ! defined operators, defined assignments,
139 # ! list of imported modules ("use" statement).
144 # end subroutine hide
146 # end module hogehoge
150 require "rdoc/code_objects"
156 NO_TEXT = "??".freeze
158 def initialize(line_no, char_no)
163 # Because we're used in contexts that expect to return a token,
164 # we set the text string and then return ourselves
170 attr_reader :line_no, :char_no, :text
174 # See rdoc/parsers/parse_f95.rb
176 class Fortran95parser
179 parse_files_matching(/\.((f|F)9(0|5)|F)$/)
181 @@external_aliases = []
182 @@public_methods = []
184 # "false":: Comments are below source code
185 # "true" :: Comments are upper source code
186 COMMENTS_ARE_UPPER = false
188 # Internal alias message
189 INTERNAL_ALIAS_MES = "Alias for"
191 # External alias message
192 EXTERNAL_ALIAS_MES = "The entity is"
194 # prepare to parse a Fortran 95 file
195 def initialize(top_level, file_name, body, options, stats)
198 @file_name = file_name
200 @top_level = top_level
201 @progress = $stderr unless options.quiet
204 # devine code constructs
207 # remove private comment
208 remaining_code = remove_private_comments(@body)
210 # continuation lines are united to one line
211 remaining_code = united_to_one_line(remaining_code)
213 # semicolons are replaced to line feed
214 remaining_code = semicolon_to_linefeed(remaining_code)
216 # collect comment for file entity
217 whole_comment, remaining_code = collect_first_comment(remaining_code)
218 @top_level.comment = whole_comment
220 # String "remaining_code" is converted to Array "remaining_lines"
221 remaining_lines = remaining_code.split("\n")
223 # "module" or "program" parts are parsed (new)
226 block_searching_flag = nil
227 block_searching_lines = []
229 module_program_trailing = ""
230 module_program_name = ""
231 other_block_level_depth = 0
232 other_block_searching_flag = nil
233 remaining_lines.collect!{|line|
234 if !block_searching_flag && !other_block_searching_flag
235 if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i
236 block_searching_flag = :module
237 block_searching_lines << line
238 module_program_name = $1
239 module_program_trailing = find_comments($2)
241 elsif line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i ||
242 line =~ /^\s*?\w/ && !block_start?(line)
243 block_searching_flag = :program
244 block_searching_lines << line
245 module_program_name = $1 || ""
246 module_program_trailing = find_comments($2)
249 elsif block_start?(line)
250 other_block_searching_flag = true
253 elsif line =~ /^\s*?!\s?(.*)/
260 elsif other_block_searching_flag
261 other_block_level_depth += 1 if block_start?(line)
262 other_block_level_depth -= 1 if block_end?(line)
263 if other_block_level_depth < 0
264 other_block_level_depth = 0
265 other_block_searching_flag = nil
270 block_searching_lines << line
271 level_depth += 1 if block_start?(line)
272 level_depth -= 1 if block_end?(line)
277 # "module_program_code" is formatted.
278 # ":nodoc:" flag is checked.
280 module_program_code = block_searching_lines.join("\n")
281 module_program_code = remove_empty_head_lines(module_program_code)
282 if module_program_trailing =~ /^:nodoc:/
283 # next loop to search next block
285 block_searching_flag = false
286 block_searching_lines = []
291 # NormalClass is created, and added to @top_level
293 if block_searching_flag == :module
294 module_name = module_program_name
295 module_code = module_program_code
296 module_trailing = module_program_trailing
298 @stats.num_modules += 1
299 f9x_module = @top_level.add_module NormalClass, module_name
300 f9x_module.record_location @top_level
302 f9x_comment = COMMENTS_ARE_UPPER ?
303 find_comments(pre_comment.join("\n")) + "\n" + module_trailing :
304 module_trailing + "\n" + find_comments(module_code.sub(/^.*$\n/i, ''))
305 f9x_module.comment = f9x_comment
306 parse_program_or_module(f9x_module, module_code)
308 TopLevel.all_files.each do |name, toplevel|
309 if toplevel.include_includes?(module_name, @options.ignore_case)
310 if !toplevel.include_requires?(@file_name, @options.ignore_case)
311 toplevel.add_require(Require.new(@file_name, ""))
314 toplevel.each_classmodule{|m|
315 if m.include_includes?(module_name, @options.ignore_case)
316 if !m.include_requires?(@file_name, @options.ignore_case)
317 m.add_require(Require.new(@file_name, ""))
322 elsif block_searching_flag == :program
323 program_name = module_program_name
324 program_code = module_program_code
325 program_trailing = module_program_trailing
327 program_comment = COMMENTS_ARE_UPPER ?
328 find_comments(pre_comment.join("\n")) + "\n" + program_trailing :
329 program_trailing + "\n" + find_comments(program_code.sub(/^.*$\n/i, ''))
330 program_comment = "\n\n= <i>Program</i> <tt>#{program_name}</tt>\n\n" \
332 @top_level.comment << program_comment
333 parse_program_or_module(@top_level, program_code, :private)
336 # next loop to search next block
338 block_searching_flag = false
339 block_searching_lines = []
344 remaining_lines.delete_if{ |line|
348 # External subprograms and functions are parsed
350 parse_program_or_module(@top_level, remaining_lines.join("\n"),
358 def parse_program_or_module(container, code,
359 visibility=:public, external=nil)
360 return unless container
362 remaining_lines = code.split("\n")
363 remaining_code = "#{code}"
366 # Parse variables before "contains" in module
369 before_contains_lines = []
370 before_contains_code = nil
371 before_contains_flag = nil
372 remaining_lines.each{ |line|
373 if !before_contains_flag
374 if line =~ /^\s*?module\s+\w+\s*?(!.*?)?$/i
375 before_contains_flag = true
378 break if line =~ /^\s*?contains\s*?(!.*?)?$/i
379 level_depth += 1 if block_start?(line)
380 level_depth -= 1 if block_end?(line)
381 break if level_depth < 0
382 before_contains_lines << line
385 before_contains_code = before_contains_lines.join("\n")
386 if before_contains_code
387 before_contains_code.gsub!(/^\s*?interface\s+.*?\s+end\s+interface.*?$/im, "")
388 before_contains_code.gsub!(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "")
394 use_check_code = "#{before_contains_code}"
395 cascaded_modules_list = []
396 while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i
397 use_check_code = $~.pre_match
398 use_check_code << $~.post_match
399 used_mod_name = $1.strip.chomp
401 used_trailing = $3 || ""
402 next if used_trailing =~ /!:nodoc:/
403 if !container.include_includes?(used_mod_name, @options.ignore_case)
405 container.add_include Include.new(used_mod_name, "")
407 if ! (used_list =~ /\,\s*?only\s*?:/i )
408 cascaded_modules_list << "\#" + used_mod_name
413 # Parse public and private, and store information.
414 # This information is used when "add_method" and
415 # "set_visibility_for" are called.
417 visibility_default, visibility_info =
418 parse_visibility(remaining_lines.join("\n"), visibility, container)
419 @@public_methods.concat visibility_info
420 if visibility_default == :public
421 if !cascaded_modules_list.empty?
423 Attr.new("Cascaded Modules",
424 "Imported modules all of whose components are published again",
426 cascaded_modules_list.join(", "))
427 container.add_attribute(cascaded_modules)
432 # Check rename elements
434 use_check_code = "#{before_contains_code}"
435 while use_check_code =~ /^\s*?use\s+(\w+)\s*?\,(.+)$/i
436 use_check_code = $~.pre_match
437 use_check_code << $~.post_match
438 used_mod_name = $1.strip.chomp
439 used_elements = $2.sub(/\s*?only\s*?:\s*?/i, '')
440 used_elements.split(",").each{ |used|
441 if /\s*?(\w+)\s*?=>\s*?(\w+)\s*?/ =~ used
444 @@public_methods.collect!{ |pub_meth|
445 if local == pub_meth["name"] ||
446 local.upcase == pub_meth["name"].upcase &&
448 pub_meth["name"] = org
449 pub_meth["local_name"] = local
458 # Parse private "use"
460 use_check_code = remaining_lines.join("\n")
461 while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i
462 use_check_code = $~.pre_match
463 use_check_code << $~.post_match
464 used_mod_name = $1.strip.chomp
465 used_trailing = $3 || ""
466 next if used_trailing =~ /!:nodoc:/
467 if !container.include_includes?(used_mod_name, @options.ignore_case)
469 container.add_include Include.new(used_mod_name, "")
473 container.each_includes{ |inc|
474 TopLevel.all_files.each do |name, toplevel|
475 indicated_mod = toplevel.find_symbol(inc.name,
476 nil, @options.ignore_case)
478 indicated_name = indicated_mod.parent.file_relative_name
479 if !container.include_requires?(indicated_name, @options.ignore_case)
480 container.add_require(Require.new(indicated_name, ""))
488 # Parse derived-types definitions
490 derived_types_comment = ""
491 remaining_code = remaining_lines.join("\n")
492 while remaining_code =~ /^\s*?
493 type[\s\,]+(public|private)?\s*?(::)?\s*?
498 remaining_code = $~.pre_match
499 remaining_code << $~.post_match
500 typename = $3.chomp.strip
501 type_elements = $5 || ""
502 type_code = remove_empty_head_lines($&)
503 type_trailing = find_comments($4)
504 next if type_trailing =~ /^:nodoc:/
506 type_comment = COMMENTS_ARE_UPPER ?
507 find_comments($~.pre_match) + "\n" + type_trailing :
508 type_trailing + "\n" + find_comments(type_code.sub(/^.*$\n/i, ''))
509 type_element_visibility_public = true
510 type_code.split("\n").each{ |line|
511 if /^\s*?private\s*?$/ =~ line
512 type_element_visibility_public = nil
521 args_comment = find_arguments(nil, type_code, true)
523 type_public_args_list = []
524 type_args_info = definition_info(type_code)
525 type_args_info.each{ |arg|
526 arg_is_public = type_element_visibility_public
527 arg_is_public = true if arg.include_attr?("public")
528 arg_is_public = nil if arg.include_attr?("private")
529 type_public_args_list << arg.varname if arg_is_public
531 args_comment = find_arguments(type_public_args_list, type_code)
534 type = AnyMethod.new("type #{typename}", typename)
535 type.singleton = false
537 type.comment = "<b><em> Derived Type </em></b> :: <tt></tt>\n"
538 type.comment << args_comment if args_comment
539 type.comment << type_comment if type_comment
541 @stats.num_methods += 1
542 container.add_method type
544 set_visibility(container, typename, visibility_default, @@public_methods)
547 type_visibility.gsub!(/\s/,'')
548 type_visibility.gsub!(/\,/,'')
549 type_visibility.gsub!(/:/,'')
550 type_visibility.downcase!
551 if type_visibility == "public"
552 container.set_visibility_for([typename], :public)
553 elsif type_visibility == "private"
554 container.set_visibility_for([typename], :private)
558 check_public_methods(type, container.name)
561 derived_types_comment << ", " unless derived_types_comment.empty?
562 derived_types_comment << typename
564 if type.visibility == :public
565 derived_types_comment << ", " unless derived_types_comment.empty?
566 derived_types_comment << typename
572 if !derived_types_comment.empty?
573 derived_types_table =
574 Attr.new("Derived Types", "Derived_Types", "",
575 derived_types_comment)
576 container.add_attribute(derived_types_table)
580 # move interface scope
583 while remaining_code =~ /^\s*?
586 \s+operator\s*?\(.*?\) |
587 \s+assignment\s*?\(\s*?=\s*?\)
590 ^\s*?end\s+interface.*?$
592 interface_code << remove_empty_head_lines($&) + "\n"
593 remaining_code = $~.pre_match
594 remaining_code << $~.post_match
598 # Parse global constants or variables in modules
600 const_var_defs = definition_info(before_contains_code)
601 const_var_defs.each{|defitem|
602 next if defitem.nodoc
603 const_or_var_type = "Variable"
604 const_or_var_progress = "v"
605 if defitem.include_attr?("parameter")
606 const_or_var_type = "Constant"
607 const_or_var_progress = "c"
609 const_or_var = AnyMethod.new(const_or_var_type, defitem.varname)
610 const_or_var.singleton = false
611 const_or_var.params = ""
612 self_comment = find_arguments([defitem.varname], before_contains_code)
613 const_or_var.comment = "<b><em>" + const_or_var_type + "</em></b> :: <tt></tt>\n"
614 const_or_var.comment << self_comment if self_comment
615 progress const_or_var_progress
616 @stats.num_methods += 1
617 container.add_method const_or_var
619 set_visibility(container, defitem.varname, visibility_default, @@public_methods)
621 if defitem.include_attr?("public")
622 container.set_visibility_for([defitem.varname], :public)
623 elsif defitem.include_attr?("private")
624 container.set_visibility_for([defitem.varname], :private)
627 check_public_methods(const_or_var, container.name)
631 remaining_lines = remaining_code.split("\n")
633 # "subroutine" or "function" parts are parsed (new)
636 block_searching_flag = nil
637 block_searching_lines = []
639 procedure_trailing = ""
641 procedure_params = ""
642 procedure_prefix = ""
643 procedure_result_arg = ""
647 remaining_lines.collect!{|line|
648 if !block_searching_flag
651 (recursive|pure|elemental)?\s*?
652 subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
654 block_searching_flag = :subroutine
655 block_searching_lines << line
657 procedure_name = $2.chomp.strip
658 procedure_params = $3 || ""
659 procedure_prefix = $1 || ""
660 procedure_trailing = $4 || "!"
665 (recursive|pure|elemental)?\s*?
667 character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
668 | type\s*?\([\w\s]+?\)\s+
669 | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
670 | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
671 | double\s+precision\s+
672 | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
673 | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
676 (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
678 block_searching_flag = :function
679 block_searching_lines << line
681 procedure_prefix = $1 || ""
682 procedure_type = $2 ? $2.chomp.strip : nil
683 procedure_name = $8.chomp.strip
684 procedure_params = $9 || ""
685 procedure_result_arg = $11 ? $11.chomp.strip : procedure_name
686 procedure_trailing = $12 || "!"
688 elsif line =~ /^\s*?!\s?(.*)/
696 contains_flag = true if line =~ /^\s*?contains\s*?(!.*?)?$/
697 block_searching_lines << line
698 contains_lines << line if contains_flag
700 level_depth += 1 if block_start?(line)
701 level_depth -= 1 if block_end?(line)
706 # "procedure_code" is formatted.
707 # ":nodoc:" flag is checked.
709 procedure_code = block_searching_lines.join("\n")
710 procedure_code = remove_empty_head_lines(procedure_code)
711 if procedure_trailing =~ /^!:nodoc:/
712 # next loop to search next block
714 block_searching_flag = nil
715 block_searching_lines = []
717 procedure_trailing = ""
719 procedure_params = ""
720 procedure_prefix = ""
721 procedure_result_arg = ""
728 # AnyMethod is created, and added to container
730 subroutine_function = nil
731 if block_searching_flag == :subroutine
732 subroutine_prefix = procedure_prefix
733 subroutine_name = procedure_name
734 subroutine_params = procedure_params
735 subroutine_trailing = procedure_trailing
736 subroutine_code = procedure_code
738 subroutine_comment = COMMENTS_ARE_UPPER ?
739 pre_comment.join("\n") + "\n" + subroutine_trailing :
740 subroutine_trailing + "\n" + subroutine_code.sub(/^.*$\n/i, '')
741 subroutine = AnyMethod.new("subroutine", subroutine_name)
742 parse_subprogram(subroutine, subroutine_params,
743 subroutine_comment, subroutine_code,
744 before_contains_code, nil, subroutine_prefix)
746 @stats.num_methods += 1
747 container.add_method subroutine
748 subroutine_function = subroutine
750 elsif block_searching_flag == :function
751 function_prefix = procedure_prefix
752 function_type = procedure_type
753 function_name = procedure_name
754 function_params_org = procedure_params
755 function_result_arg = procedure_result_arg
756 function_trailing = procedure_trailing
757 function_code_org = procedure_code
759 function_comment = COMMENTS_ARE_UPPER ?
760 pre_comment.join("\n") + "\n" + function_trailing :
761 function_trailing + "\n " + function_code_org.sub(/^.*$\n/i, '')
763 function_code = "#{function_code_org}"
765 function_code << "\n" + function_type + " :: " + function_result_arg
769 function_params_org.sub(/^\(/, "\(#{function_result_arg}, ")
771 function = AnyMethod.new("function", function_name)
772 parse_subprogram(function, function_params,
773 function_comment, function_code,
774 before_contains_code, true, function_prefix)
776 # Specific modification due to function
777 function.params.sub!(/\(\s*?#{function_result_arg}\s*?,\s*?/, "\( ")
778 function.params << " result(" + function_result_arg + ")"
779 function.start_collecting_tokens
780 function.add_token Token.new(1,1).set_text(function_code_org)
783 @stats.num_methods += 1
784 container.add_method function
785 subroutine_function = function
789 # The visibility of procedure is specified
791 set_visibility(container, procedure_name,
792 visibility_default, @@public_methods)
794 # The alias for this procedure from external modules
796 check_external_aliases(procedure_name,
797 subroutine_function.params,
798 subroutine_function.comment, subroutine_function) if external
799 check_public_methods(subroutine_function, container.name)
802 # contains_lines are parsed as private procedures
804 parse_program_or_module(container,
805 contains_lines.join("\n"), :private)
808 # next loop to search next block
810 block_searching_flag = nil
811 block_searching_lines = []
813 procedure_trailing = ""
815 procedure_params = ""
816 procedure_prefix = ""
817 procedure_result_arg = ""
821 } # End of remaining_lines.collect!{|line|
823 # Array remains_lines is converted to String remains_code again
825 remaining_code = remaining_lines.join("\n")
830 interface_scope = false
832 interface_code.split("\n").each{ |line|
836 \s+operator\s*?\(.*?\)|
837 \s+assignment\s*?\(\s*?=\s*?\)
841 generic_name = $1 ? $1.strip.chomp : nil
842 interface_trailing = $2 || "!"
843 interface_scope = true
844 interface_scope = false if interface_trailing =~ /!:nodoc:/
845 # if generic_name =~ /operator\s*?\((.*?)\)/i
847 # if operator_name && !operator_name.empty?
848 # generic_name = "#{operator_name}"
851 # if generic_name =~ /assignment\s*?\((.*?)\)/i
852 # assignment_name = $1
853 # if assignment_name && !assignment_name.empty?
854 # generic_name = "#{assignment_name}"
858 if /^\s*?end\s+interface/i =~ line
859 interface_scope = false
863 if interface_scope && /^\s*?module\s+procedure\s+(.*?)(!.*?)?$/i =~ line
864 procedures = $1.strip.chomp
865 procedures_trailing = $2 || "!"
866 next if procedures_trailing =~ /!:nodoc:/
867 procedures.split(",").each{ |proc|
870 next if generic_name == proc || !generic_name
871 old_meth = container.find_symbol(proc, nil, @options.ignore_case)
873 nolink = old_meth.visibility == :private ? true : nil
874 nolink = nil if @options.show_all
876 initialize_external_method(generic_name, proc,
877 old_meth.params, nil,
879 old_meth.clone.token_stream[0].text,
881 new_meth.singleton = old_meth.singleton
884 @stats.num_methods += 1
885 container.add_method new_meth
887 set_visibility(container, generic_name, visibility_default, @@public_methods)
889 check_public_methods(new_meth, container.name)
899 procedures_trailing = nil
901 (recursive|pure|elemental)?\s*?
902 subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
904 proc = $2.chomp.strip
905 generic_name = proc unless generic_name
907 procedures_trailing = $4 || "!"
911 (recursive|pure|elemental)?\s*?
913 character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
914 | type\s*?\([\w\s]+?\)\s+
915 | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
916 | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
917 | double\s+precision\s+
918 | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
919 | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
922 (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
924 proc = $8.chomp.strip
925 generic_name = proc unless generic_name
927 procedures_trailing = $12 || "!"
931 next if procedures_trailing =~ /!:nodoc:/
932 indicated_method = nil
934 TopLevel.all_files.each do |name, toplevel|
935 indicated_method = toplevel.find_local_symbol(proc, @options.ignore_case)
936 indicated_file = name
937 break if indicated_method
942 initialize_external_method(generic_name, proc,
943 indicated_method.params,
945 indicated_method.comment)
948 @stats.num_methods += 1
949 container.add_method external_method
950 set_visibility(container, generic_name, visibility_default, @@public_methods)
951 if !container.include_requires?(indicated_file, @options.ignore_case)
952 container.add_require(Require.new(indicated_file, ""))
954 check_public_methods(external_method, container.name)
957 @@external_aliases << {
958 "new_name" => generic_name,
960 "file_or_module" => container,
961 "visibility" => find_visibility(container, generic_name, @@public_methods) || visibility_default
966 } if interface_code # End of interface_code.split("\n").each ...
969 # Already imported methods are removed from @@public_methods.
970 # Remainders are assumed to be imported from other modules.
972 @@public_methods.delete_if{ |method| method["entity_is_discovered"]}
974 @@public_methods.each{ |pub_meth|
975 next unless pub_meth["file_or_module"].name == container.name
976 pub_meth["used_modules"].each{ |used_mod|
977 TopLevel.all_classes_and_modules.each{ |modules|
978 if modules.name == used_mod ||
979 modules.name.upcase == used_mod.upcase &&
981 modules.method_list.each{ |meth|
982 if meth.name == pub_meth["name"] ||
983 meth.name.upcase == pub_meth["name"].upcase &&
985 new_meth = initialize_public_method(meth,
987 if pub_meth["local_name"]
988 new_meth.name = pub_meth["local_name"]
991 @stats.num_methods += 1
992 container.add_method new_meth
1001 end # End of parse_program_or_module
1004 # Parse arguments, comment, code of subroutine and function.
1005 # Return AnyMethod object.
1007 def parse_subprogram(subprogram, params, comment, code,
1008 before_contains=nil, function=nil, prefix=nil)
1009 subprogram.singleton = false
1010 prefix = "" if !prefix
1011 arguments = params.sub(/\(/, "").sub(/\)/, "").split(",") if params
1012 args_comment, params_opt =
1013 find_arguments(arguments, code.sub(/^s*?contains\s*?(!.*?)?$.*/im, ""),
1015 params_opt = "( " + params_opt + " ) " if params_opt
1016 subprogram.params = params_opt || ""
1017 namelist_comment = find_namelists(code, before_contains)
1019 block_comment = find_comments comment
1021 subprogram.comment = "<b><em> Function </em></b> :: <em>#{prefix}</em>\n"
1023 subprogram.comment = "<b><em> Subroutine </em></b> :: <em>#{prefix}</em>\n"
1025 subprogram.comment << args_comment if args_comment
1026 subprogram.comment << block_comment if block_comment
1027 subprogram.comment << namelist_comment if namelist_comment
1029 # For output source code
1030 subprogram.start_collecting_tokens
1031 subprogram.add_token Token.new(1,1).set_text(code)
1037 # Collect comment for file entity
1039 def collect_first_comment(body)
1042 comment_start = false
1044 body.split("\n").each{ |line|
1048 elsif /^\s*?!\s?(.*)$/i =~ line
1049 comment_start = true
1052 elsif /^\s*?$/i =~ line
1053 comment_end = true if comment_start && COMMENTS_ARE_UPPER
1060 return comment, not_comment
1064 # Return comments of definitions of arguments
1066 # If "all" argument is true, information of all arguments are returned.
1067 # If "modified_params" is true, list of arguments are decorated,
1068 # for exameple, optional arguments are parenthetic as "[arg]".
1070 def find_arguments(args, text, all=nil, indent=nil, modified_params=nil)
1071 return unless args || all
1072 indent = "" unless indent
1073 args = ["all"] if all
1074 params = "" if modified_params
1077 args_rdocforms = "\n"
1078 remaining_lines = "#{text}"
1079 definitions = definition_info(remaining_lines)
1083 definitions.each { |defitem|
1084 if arg == defitem.varname.strip.chomp || all
1085 args_rdocforms << <<-"EOF"
1087 #{indent}<tt><b>#{defitem.varname.chomp.strip}#{defitem.arraysuffix}</b> #{defitem.inivalue}</tt> ::
1088 #{indent} <tt>#{defitem.types.chomp.strip}</tt>
1090 if !defitem.comment.chomp.strip.empty?
1092 defitem.comment.split("\n").each{ |line|
1093 comment << " " + line + "\n"
1095 args_rdocforms << <<-"EOF"
1097 #{indent} <tt></tt> ::
1099 #{indent} #{comment.chomp.strip}
1104 if defitem.include_attr?("optional")
1105 params << "#{comma}[#{arg}]"
1107 params << "#{comma}#{arg}"
1115 return args_rdocforms, params
1117 return args_rdocforms
1121 # Return comments of definitions of namelists
1123 def find_namelists(text, before_contains=nil)
1127 before_contains = "" if !before_contains
1128 while lines =~ /^\s*?namelist\s+\/\s*?(\w+)\s*?\/([\s\w\,]+)$/i
1129 lines = $~.post_match
1130 nml_comment = COMMENTS_ARE_UPPER ?
1131 find_comments($~.pre_match) : find_comments($~.post_match)
1133 nml_args = $2.split(",")
1134 result << "\n\n=== NAMELIST <tt><b>" + nml_name + "</tt></b>\n\n"
1135 result << nml_comment + "\n" if nml_comment
1136 if lines.split("\n")[0] =~ /^\//i
1137 lines = "namelist " + lines
1139 result << find_arguments(nml_args, "#{text}" + "\n" + before_contains)
1145 # Comments just after module or subprogram, or arguments are
1146 # returnd. If "COMMENTS_ARE_UPPER" is true, comments just before
1147 # modules or subprograms are returnd
1149 def find_comments text
1150 return "" unless text
1151 lines = text.split("\n")
1152 lines.reverse! if COMMENTS_ARE_UPPER
1153 comment_block = Array.new
1154 lines.each do |line|
1155 break if line =~ /^\s*?\w/ || line =~ /^\s*?$/
1156 if COMMENTS_ARE_UPPER
1157 comment_block.unshift line.sub(/^\s*?!\s?/,"")
1159 comment_block.push line.sub(/^\s*?!\s?/,"")
1162 nice_lines = comment_block.join("\n").split "\n\s*?\n"
1163 nice_lines[0] ||= ""
1168 unless @options.quiet
1169 @progress.print(char)
1175 # Create method for internal alias
1177 def initialize_public_method(method, parent)
1178 return if !method || !parent
1180 new_meth = AnyMethod.new("External Alias for module", method.name)
1181 new_meth.singleton = method.singleton
1182 new_meth.params = method.params.clone
1183 new_meth.comment = remove_trailing_alias(method.comment.clone)
1184 new_meth.comment << "\n\n#{EXTERNAL_ALIAS_MES} #{parent.strip.chomp}\##{method.name}"
1190 # Create method for external alias
1192 # If argument "internal" is true, file is ignored.
1194 def initialize_external_method(new, old, params, file, comment, token=nil,
1195 internal=nil, nolink=nil)
1196 return nil unless new || old
1199 external_alias_header = "#{INTERNAL_ALIAS_MES} "
1200 external_alias_text = external_alias_header + old
1202 external_alias_header = "#{EXTERNAL_ALIAS_MES} "
1203 external_alias_text = external_alias_header + file + "#" + old
1207 external_meth = AnyMethod.new(external_alias_text, new)
1208 external_meth.singleton = false
1209 external_meth.params = params
1210 external_comment = remove_trailing_alias(comment) + "\n\n" if comment
1211 external_meth.comment = external_comment || ""
1213 external_meth.start_collecting_tokens
1214 external_meth.add_token Token.new(1,1).set_text(token)
1216 external_meth.comment << external_alias_text
1219 return external_meth
1227 def parse_visibility(code, default, container)
1229 visibility_default = default || :public
1232 container.includes.each{|i| used_modules << i.name} if container
1234 remaining_code = code.gsub(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "")
1235 remaining_code.split("\n").each{ |line|
1236 if /^\s*?private\s*?$/ =~ line
1237 visibility_default = :private
1242 remaining_code.split("\n").each{ |line|
1243 if /^\s*?private\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line
1244 methods = $2.sub(/!.*$/, '')
1245 methods.split(",").each{ |meth|
1246 meth.sub!(/!.*$/, '')
1249 "name" => meth.chomp.strip,
1250 "visibility" => :private,
1251 "used_modules" => used_modules.clone,
1252 "file_or_module" => container,
1253 "entity_is_discovered" => nil,
1257 elsif /^\s*?public\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line
1258 methods = $2.sub(/!.*$/, '')
1259 methods.split(",").each{ |meth|
1260 meth.sub!(/!.*$/, '')
1263 "name" => meth.chomp.strip,
1264 "visibility" => :public,
1265 "used_modules" => used_modules.clone,
1266 "file_or_module" => container,
1267 "entity_is_discovered" => nil,
1275 result.each{ |vis_info|
1276 vis_info["parent"] = container.name
1280 return visibility_default, result
1286 # "subname" element of "visibility_info" is deleted.
1288 def set_visibility(container, subname, visibility_default, visibility_info)
1289 return unless container || subname || visibility_default || visibility_info
1291 visibility_info.collect!{ |info|
1292 if info["name"] == subname ||
1293 @options.ignore_case && info["name"].upcase == subname.upcase
1294 if info["file_or_module"].name == container.name
1295 container.set_visibility_for([subname], info["visibility"])
1296 info["entity_is_discovered"] = true
1303 return container.set_visibility_for([subname], visibility_default)
1312 def find_visibility(container, subname, visibility_info)
1313 return nil if !subname || !visibility_info
1314 visibility_info.each{ |info|
1315 if info["name"] == subname ||
1316 @options.ignore_case && info["name"].upcase == subname.upcase
1317 if info["parent"] == container.name
1318 return info["visibility"]
1326 # Check external aliases
1328 def check_external_aliases(subname, params, comment, test=nil)
1329 @@external_aliases.each{ |alias_item|
1330 if subname == alias_item["old_name"] ||
1331 subname.upcase == alias_item["old_name"].upcase &&
1332 @options.ignore_case
1334 new_meth = initialize_external_method(alias_item["new_name"],
1335 subname, params, @file_name,
1337 new_meth.visibility = alias_item["visibility"]
1340 @stats.num_methods += 1
1341 alias_item["file_or_module"].add_method(new_meth)
1343 if !alias_item["file_or_module"].include_requires?(@file_name, @options.ignore_case)
1344 alias_item["file_or_module"].add_require(Require.new(@file_name, ""))
1351 # Check public_methods
1353 def check_public_methods(method, parent)
1354 return if !method || !parent
1355 @@public_methods.each{ |alias_item|
1356 parent_is_used_module = nil
1357 alias_item["used_modules"].each{ |used_module|
1358 if used_module == parent ||
1359 used_module.upcase == parent.upcase &&
1360 @options.ignore_case
1361 parent_is_used_module = true
1364 next if !parent_is_used_module
1366 if method.name == alias_item["name"] ||
1367 method.name.upcase == alias_item["name"].upcase &&
1368 @options.ignore_case
1370 new_meth = initialize_public_method(method, parent)
1371 if alias_item["local_name"]
1372 new_meth.name = alias_item["local_name"]
1376 @stats.num_methods += 1
1377 alias_item["file_or_module"].add_method new_meth
1383 # Continuous lines are united.
1385 # Comments in continuous lines are removed.
1387 def united_to_one_line(f90src)
1388 return "" unless f90src
1389 lines = f90src.split("\n")
1390 previous_continuing = false
1391 now_continuing = false
1394 words = line.split("")
1395 next if words.empty? && previous_continuing
1397 brank_flag = true ; brank_char = ""
1398 squote = false ; dquote = false
1400 words.collect! { |char|
1401 if previous_continuing && brank_flag
1402 now_continuing = true
1406 when " " ; brank_char << char ; next ""
1409 now_continuing = false
1413 now_continuing = false
1415 next brank_char + char
1422 elsif !(squote) && !(dquote) && !(commentout)
1424 when "!" ; commentout = true ; next char
1425 when "\""; dquote = true ; next char
1426 when "\'"; squote = true ; next char
1427 when "&" ; now_continuing = true ; next ""
1434 when "\'"; squote = false ; next char
1439 when "\""; dquote = false ; next char
1444 if !ignore && !previous_continuing || !brank_flag
1445 if previous_continuing
1446 body << words.join("")
1448 body << "\n" + words.join("")
1451 previous_continuing = now_continuing ? true : nil
1452 now_continuing = nil
1459 # Continuous line checker
1461 def continuous_line?(line)
1463 if /&\s*?(!.*)?$/ =~ line
1465 if comment_out?($~.pre_match)
1473 # Comment out checker
1475 def comment_out?(line)
1476 return nil unless line
1478 squote = false ; dquote = false
1479 line.split("").each { |char|
1480 if !(squote) && !(dquote)
1482 when "!" ; commentout = true ; break
1483 when "\""; dquote = true
1484 when "\'"; squote = true
1489 when "\'"; squote = false
1494 when "\""; dquote = false
1503 # Semicolons are replaced to line feed.
1505 def semicolon_to_linefeed(text)
1506 return "" unless text
1507 lines = text.split("\n")
1508 lines.collect!{ |line|
1509 words = line.split("")
1511 squote = false ; dquote = false
1512 words.collect! { |char|
1513 if !(squote) && !(dquote) && !(commentout)
1515 when "!" ; commentout = true ; next char
1516 when "\""; dquote = true ; next char
1517 when "\'"; squote = true ; next char
1525 when "\'"; squote = false ; next char
1530 when "\""; dquote = false ; next char
1537 return lines.join("\n")
1541 # Which "line" is start of block (module, program, block data,
1542 # subroutine, function) statement ?
1544 def block_start?(line)
1547 if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i ||
1548 line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i ||
1549 line =~ /^\s*?block\s+data(\s+\w+)?\s*?(!.*?)?$/i ||
1552 (recursive|pure|elemental)?\s*?
1553 subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
1557 (recursive|pure|elemental)?\s*?
1559 character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
1560 | type\s*?\([\w\s]+?\)\s+
1561 | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
1562 | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
1563 | double\s+precision\s+
1564 | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
1565 | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
1567 function\s+(\w+)\s*?
1568 (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
1577 # Which "line" is end of block (module, program, block data,
1578 # subroutine, function) statement ?
1580 def block_end?(line)
1583 if line =~ /^\s*?end\s*?(!.*?)?$/i ||
1584 line =~ /^\s*?end\s+module(\s+\w+)?\s*?(!.*?)?$/i ||
1585 line =~ /^\s*?end\s+program(\s+\w+)?\s*?(!.*?)?$/i ||
1586 line =~ /^\s*?end\s+block\s+data(\s+\w+)?\s*?(!.*?)?$/i ||
1587 line =~ /^\s*?end\s+subroutine(\s+\w+)?\s*?(!.*?)?$/i ||
1588 line =~ /^\s*?end\s+function(\s+\w+)?\s*?(!.*?)?$/i
1596 # Remove "Alias for" in end of comments
1598 def remove_trailing_alias(text)
1600 lines = text.split("\n").reverse
1601 comment_block = Array.new
1603 lines.each do |line|
1605 if /^\s?#{INTERNAL_ALIAS_MES}/ =~ line ||
1606 /^\s?#{EXTERNAL_ALIAS_MES}/ =~ line
1611 comment_block.unshift line
1613 nice_lines = comment_block.join("\n")
1618 # Empty lines in header are removed
1619 def remove_empty_head_lines(text)
1620 return "" unless text
1621 lines = text.split("\n")
1623 lines.delete_if{ |line|
1624 header = false if /\S/ =~ line
1625 header && /^\s*?$/ =~ line
1631 # header marker "=", "==", ... are removed
1632 def remove_header_marker(text)
1633 return text.gsub(/^\s?(=+)/, '<tt></tt>\1')
1636 def remove_private_comments(body)
1637 body.gsub!(/^\s*!--\s*?$.*?^\s*!\+\+\s*?$/m, '')
1643 # Information of arguments of subroutines and functions in Fortran95
1645 class Fortran95Definition
1649 attr_reader :varname
1657 attr_reader :inivalue
1661 attr_reader :arraysuffix
1665 attr_accessor :comment
1667 # Flag of non documentation
1669 attr_accessor :nodoc
1671 def initialize(varname, types, inivalue, arraysuffix, comment,
1675 @inivalue = inivalue
1676 @arraysuffix = arraysuffix
1683 <Fortran95Definition:
1684 varname=#{@varname}, types=#{types},
1685 inivalue=#{@inivalue}, arraysuffix=#{@arraysuffix}, nodoc=#{@nodoc},
1693 # If attr is included, true is returned
1695 def include_attr?(attr)
1697 @types.split(",").each{ |type|
1698 return true if type.strip.chomp.upcase == attr.strip.chomp.upcase
1703 end # End of Fortran95Definition
1706 # Parse string argument "text", and Return Array of
1707 # Fortran95Definition object
1709 def definition_info(text)
1710 return nil unless text
1714 trailing_comment = ""
1715 under_comment_valid = false
1716 lines.split("\n").each{ |line|
1717 if /^\s*?!\s?(.*)/ =~ line
1718 if COMMENTS_ARE_UPPER
1719 comment << remove_header_marker($1)
1721 elsif defs[-1] && under_comment_valid
1722 defs[-1].comment << "\n"
1723 defs[-1].comment << remove_header_marker($1)
1726 elsif /^\s*?$/ =~ line
1728 under_comment_valid = false
1735 character\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
1736 | type\s*?\([\w\s]+?\)[\s\,]*
1737 | integer\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
1738 | real\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
1739 | double\s+precision[\s\,]*
1740 | logical\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
1741 | complex\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
1748 type << $7.gsub(/::/, '').gsub(/^\s*?\,/, '') if $7
1750 under_comment_valid = false
1753 squote = false ; dquote = false ; bracket = 0
1754 iniflag = false; commentflag = false
1755 varname = "" ; arraysuffix = "" ; inivalue = ""
1756 start_pos = defs.size
1757 characters.split("").each { |char|
1758 if !(squote) && !(dquote) && bracket <= 0 && !(iniflag) && !(commentflag)
1760 when "!" ; commentflag = true
1761 when "(" ; bracket += 1 ; arraysuffix = char
1762 when "\""; dquote = true
1763 when "\'"; squote = true
1764 when "=" ; iniflag = true ; inivalue << char
1766 defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
1767 varname = "" ; arraysuffix = "" ; inivalue = ""
1768 under_comment_valid = true
1770 else ; varname << char
1773 comment << remove_header_marker(char)
1774 trailing_comment << remove_header_marker(char)
1778 when "\"" ; dquote = false ; inivalue << char
1779 else ; inivalue << char
1783 when "\'" ; squote = false ; inivalue << char
1784 else ; inivalue << char
1788 when "(" ; bracket += 1 ; inivalue << char
1789 when ")" ; bracket -= 1 ; inivalue << char
1790 else ; inivalue << char
1795 defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
1796 varname = "" ; arraysuffix = "" ; inivalue = ""
1798 under_comment_valid = true
1799 when "(" ; bracket += 1 ; inivalue << char
1800 when "\""; dquote = true ; inivalue << char
1801 when "\'"; squote = true ; inivalue << char
1802 when "!" ; commentflag = true
1803 else ; inivalue << char
1806 elsif !(squote) && !(dquote) && bracket > 0
1808 when "(" ; bracket += 1 ; arraysuffix << char
1809 when ")" ; bracket -= 1 ; arraysuffix << char
1810 else ; arraysuffix << char
1814 when "\'"; squote = false ; inivalue << char
1815 else ; inivalue << char
1819 when "\""; dquote = false ; inivalue << char
1820 else ; inivalue << char
1824 defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
1825 if trailing_comment =~ /^:nodoc:/
1826 defs[start_pos..-1].collect!{ |defitem|
1827 defitem.nodoc = true
1830 varname = "" ; arraysuffix = "" ; inivalue = ""
1832 under_comment_valid = true
1833 trailing_comment = ""
1839 end # class Fortran95parser