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