diff --git a/src/framework/MOM_file_parser.F90 b/src/framework/MOM_file_parser.F90 index 3ad551496f..853d3c4de4 100644 --- a/src/framework/MOM_file_parser.F90 +++ b/src/framework/MOM_file_parser.F90 @@ -16,13 +16,14 @@ module MOM_file_parser implicit none ; private +! These are hard-coded limits that are used in the following code. They should be set +! generously enough not to impose any significant limitations. integer, parameter, public :: MAX_PARAM_FILES = 5 !< Maximum number of parameter files. -integer, parameter :: INPUT_STR_LENGTH = 320 !< Maximum line length in parameter file. -integer, parameter :: FILENAME_LENGTH = 200 !< Maximum number of characters in file names. +integer, parameter :: INPUT_STR_LENGTH = 1024 !< Maximum line length in parameter file. Lines that + !! are combined by ending in '\' or '&' can exceed + !! this limit after merging. +integer, parameter :: FILENAME_LENGTH = 200 !< Maximum number of characters in file names. -! The all_PEs_read option should be eliminated with post-riga shared code. -logical :: all_PEs_read = .false. !< If true, all PEs read the input files - !! TODO: Eliminate this parameter !>@{ Default values for parameters logical, parameter :: report_unused_default = .true. @@ -32,11 +33,17 @@ module MOM_file_parser logical, parameter :: minimal_doc_default = .true. !>@} + +!> A simple type to allow lines in an array to be allocated with variable sizes. +type, private :: file_line_type ; private + character(len=:), allocatable :: line !< An allocatable line with content +end type file_line_type + !> The valid lines extracted from an input parameter file without comments type, private :: file_data_type ; private integer :: num_lines = 0 !< The number of lines in this type - character(len=INPUT_STR_LENGTH), pointer, dimension(:) :: line => NULL() !< The line content - logical, pointer, dimension(:) :: line_used => NULL() !< If true, the line has been read + type(file_line_type), allocatable, dimension(:) :: fln !< Lines with the input content. + logical, pointer, dimension(:) :: line_used => NULL() !< If true, the line has been read end type file_data_type !> A link in the list of variables that have already had override warnings issued @@ -69,6 +76,9 @@ module MOM_file_parser logical :: log_to_stdout = log_to_stdout_default !< If true, all log !! messages are also sent to stdout. logical :: log_open = .false. !< True if the log file has been opened. + integer :: max_line_len = 4 !< The maximum number of characters in the lines + !! in any of the files in this param_file_type after + !! any continued lines have been combined. integer :: stdout !< The unit number from stdout(). integer :: stdlog !< The unit number from stdlog(). character(len=240) :: doc_file !< A file where all run-time parameters, their @@ -173,11 +183,10 @@ subroutine open_param_file(filename, CS, checkable, component, doc_file_dir) if (Netcdf_file) & call MOM_error(FATAL,"open_param_file: NetCDF files are not yet supported.") - if (all_PEs_read .or. is_root_pe()) then + if (is_root_pe()) then open(newunit=iounit, file=trim(filename), access='SEQUENTIAL', & form='FORMATTED', action='READ', position='REWIND', iostat=ios) - if (ios /= 0) call MOM_error(FATAL, "open_param_file: Error opening '"// & - trim(filename)//"'.") + if (ios /= 0) call MOM_error(FATAL, "open_param_file: Error opening '"//trim(filename)//"'.") else iounit = 1 endif @@ -192,10 +201,11 @@ subroutine open_param_file(filename, CS, checkable, component, doc_file_dir) if (associated(CS%blockName)) deallocate(CS%blockName) allocate(block) ; block%name = '' ; CS%blockName => block - call MOM_mesg("open_param_file: "// trim(filename)// & - " has been opened successfully.", 5) + call MOM_mesg("open_param_file: "// trim(filename)//" has been opened successfully.", 5) call populate_param_data(iounit, filename, CS%param_data(i)) + ! Increment the maximum line length, but always report values in blocks of 4 characters. + CS%max_line_len = max(CS%max_line_len, 4 + 4*(max_input_line_length(CS, i) - 1) / 4) call read_param(CS,"SEND_LOG_TO_STDOUT",CS%log_to_stdout) call read_param(CS,"REPORT_UNUSED_PARAMS",CS%report_unused) @@ -249,13 +259,14 @@ subroutine close_param_file(CS, quiet_close, component) if (present(quiet_close)) then ; if (quiet_close) then do i = 1, CS%nfiles - if (all_PEs_read .or. is_root_pe()) close(CS%iounit(i)) + if (is_root_pe()) close(CS%iounit(i)) call MOM_mesg("close_param_file: "// trim(CS%filename(i))// & " has been closed successfully.", 5) CS%iounit(i) = -1 CS%filename(i) = '' CS%NetCDF_file(i) = .false. - deallocate (CS%param_data(i)%line) + do n=1,CS%param_data(i)%num_lines ; deallocate(CS%param_data(i)%fln(n)%line) ; enddo + deallocate (CS%param_data(i)%fln) deallocate (CS%param_data(i)%line_used) enddo CS%log_open = .false. @@ -313,18 +324,18 @@ subroutine close_param_file(CS, quiet_close, component) num_unused = num_unused + 1 if (CS%report_unused) & call MOM_error(WARNING, "Unused line in "//trim(CS%filename(i))// & - " : "//trim(CS%param_data(i)%line(n))) + " : "//trim(CS%param_data(i)%fln(n)%line)) endif enddo endif - if (all_PEs_read .or. is_root_pe()) close(CS%iounit(i)) - call MOM_mesg("close_param_file: "// trim(CS%filename(i))// & - " has been closed successfully.", 5) + if (is_root_pe()) close(CS%iounit(i)) + call MOM_mesg("close_param_file: "// trim(CS%filename(i))//" has been closed successfully.", 5) CS%iounit(i) = -1 CS%filename(i) = '' CS%NetCDF_file(i) = .false. - deallocate (CS%param_data(i)%line) + do n=1,CS%param_data(i)%num_lines ; deallocate(CS%param_data(i)%fln(n)%line) ; enddo + deallocate (CS%param_data(i)%fln) deallocate (CS%param_data(i)%line_used) enddo deallocate(CS%blockName) @@ -347,19 +358,19 @@ subroutine populate_param_data(iounit, filename, param_data) ! Local variables character(len=INPUT_STR_LENGTH) :: line - integer :: num_lines + character(len=1), allocatable, dimension(:) :: char_buf + integer, allocatable, dimension(:) :: line_len ! The trimmed length of each processed input line + integer :: n, num_lines, total_chars, ch, rsc, llen, int_buf(2) logical :: inMultiLineComment ! Find the number of keyword lines in a parameter file - ! Allocate the space to hold the lines in param_data%line - ! Populate param_data%line with the keyword lines from parameter file - - if (all_PEs_read .or. is_root_pe()) then + if (is_root_pe()) then ! rewind the parameter file rewind(iounit) ! count the number of valid entries in the parameter file num_lines = 0 + total_chars = 0 inMultiLineComment = .false. do while(.true.) read(iounit, '(a)', end=8) line @@ -367,7 +378,12 @@ subroutine populate_param_data(iounit, filename, param_data) if (inMultiLineComment) then if (closeMultiLineComment(line)) inMultiLineComment=.false. else - if (lastNonCommentNonBlank(line)>0) num_lines = num_lines + 1 + if (lastNonCommentNonBlank(line)>0) then + line = removeComments(line) + line = simplifyWhiteSpace(line(:len_trim(line))) + num_lines = num_lines + 1 + total_chars = total_chars + len_trim(line) + endif if (openMultiLineComment(line)) inMultiLineComment=.true. endif enddo ! while (.true.) @@ -377,29 +393,29 @@ subroutine populate_param_data(iounit, filename, param_data) call MOM_error(FATAL, 'MOM_file_parser : A C-style multi-line comment '// & '(/* ... */) was not closed before the end of '//trim(filename)) - ! allocate space to hold contents of the parameter file - param_data%num_lines = num_lines + + int_buf(1) = num_lines + int_buf(2) = total_chars endif ! (is_root_pe()) ! Broadcast the number of valid entries in parameter file - if (.not. all_PEs_read) then - call broadcast(param_data%num_lines, root_pe()) - endif + call broadcast(int_buf, 2, root_pe()) + num_lines = int_buf(1) + total_chars = int_buf(2) ! Set up the space for storing the actual lines. - num_lines = param_data%num_lines - allocate (param_data%line(num_lines)) - allocate (param_data%line_used(num_lines)) - param_data%line(:) = ' ' - param_data%line_used(:) = .false. + param_data%num_lines = num_lines + allocate (line_len(num_lines), source=0) + allocate (char_buf(total_chars), source=" ") ! Read the actual lines. - if (all_PEs_read .or. is_root_pe()) then + if (is_root_pe()) then ! rewind the parameter file rewind(iounit) - ! Populate param_data%line + ! Populate param_data%fln%line num_lines = 0 + rsc = 0 do while(.true.) read(iounit, '(a)', end=18) line line = replaceTabs(line) @@ -408,9 +424,16 @@ subroutine populate_param_data(iounit, filename, param_data) else if (lastNonCommentNonBlank(line)>0) then line = removeComments(line) + if ((len_trim(line) > 1000) .and. is_root_PE()) then + call MOM_error(WARNING, "MOM_file_parser: Consider using continuation to split up "//& + "the excessivley long parameter input line "//trim(line)) + endif line = simplifyWhiteSpace(line(:len_trim(line))) num_lines = num_lines + 1 - param_data%line(num_lines) = line + llen = len_trim(line) + line_len(num_lines) = llen + do ch=1,llen ; char_buf(rsc+ch)(1:1) = line(ch:ch) ; enddo + rsc = rsc + llen endif if (openMultiLineComment(line)) inMultiLineComment=.true. endif @@ -422,10 +445,25 @@ subroutine populate_param_data(iounit, filename, param_data) // 'reading of '//trim(filename)) endif ! (is_root_pe()) - ! Broadcast the populated array param_data%line - if (.not. all_PEs_read) then - call broadcast(param_data%line, INPUT_STR_LENGTH, root_pe()) - endif + ! Broadcast the populated arrays line_len and char_buf + call broadcast(line_len, num_lines, root_pe()) + call broadcast(char_buf(1:total_chars), 1, root_pe()) + + ! Allocate space to hold contents of the parameter file, including the lines in param_data%fln + allocate(param_data%fln(num_lines)) + allocate(param_data%line_used(num_lines)) + param_data%line_used(:) = .false. + ! Populate param_data%fln%line with the keyword lines from parameter file + rsc = 0 + do n=1,num_lines + line(1:INPUT_STR_LENGTH) = " " + do ch=1,line_len(n) ; line(ch:ch) = char_buf(rsc+ch)(1:1) ; enddo + param_data%fln(n)%line = trim(line) + rsc = rsc + line_len(n) + enddo + + deallocate(char_buf) ; deallocate(line_len) + end subroutine populate_param_data @@ -574,7 +612,7 @@ subroutine read_param_int(CS, varname, value, fail_if_missing) logical, optional, intent(in) :: fail_if_missing !< If present and true, a fatal error occurs !! if this variable is not found in the parameter file ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) + character(len=CS%max_line_len) :: value_string(1) logical :: found, defined call get_variable_line(CS, varname, found, defined, value_string) @@ -606,7 +644,7 @@ subroutine read_param_int_array(CS, varname, value, fail_if_missing) logical, optional, intent(in) :: fail_if_missing !< If present and true, a fatal error occurs !! if this variable is not found in the parameter file ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) + character(len=CS%max_line_len) :: value_string(1) logical :: found, defined call get_variable_line(CS, varname, found, defined, value_string) @@ -642,7 +680,7 @@ subroutine read_param_real(CS, varname, value, fail_if_missing, scale) !! by before it is returned. ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) + character(len=CS%max_line_len) :: value_string(1) logical :: found, defined call get_variable_line(CS, varname, found, defined, value_string) @@ -678,8 +716,8 @@ subroutine read_param_real_array(CS, varname, value, fail_if_missing, scale) !! by before it is returned. ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) - logical :: found, defined + character(len=CS%max_line_len) :: value_string(1) + logical :: found, defined call get_variable_line(CS, varname, found, defined, value_string) if (found .and. defined .and. (LEN_TRIM(value_string(1)) > 0)) then @@ -713,15 +751,14 @@ subroutine read_param_char(CS, varname, value, fail_if_missing) logical, optional, intent(in) :: fail_if_missing !< If present and true, a fatal error occurs !! if this variable is not found in the parameter file ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) + character(len=CS%max_line_len) :: value_string(1) logical :: found, defined call get_variable_line(CS, varname, found, defined, value_string) if (found) then value = trim(strip_quotes(value_string(1))) elseif (present(fail_if_missing)) then ; if (fail_if_missing) then - call MOM_error(FATAL,'Unable to find variable '//trim(varname)// & - ' in any input files.') + call MOM_error(FATAL, 'Unable to find variable '//trim(varname)//' in any input files.') endif ; endif end subroutine read_param_char @@ -737,7 +774,7 @@ subroutine read_param_char_array(CS, varname, value, fail_if_missing) !! if this variable is not found in the parameter file ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1), loc_string + character(len=CS%max_line_len) :: value_string(1), loc_string logical :: found, defined integer :: i, i_out @@ -758,8 +795,7 @@ subroutine read_param_char_array(CS, varname, value, fail_if_missing) endif do i=i_out,SIZE(value) ; value(i) = " " ; enddo elseif (present(fail_if_missing)) then ; if (fail_if_missing) then - call MOM_error(FATAL,'Unable to find variable '//trim(varname)// & - ' in any input files.') + call MOM_error(FATAL, 'Unable to find variable '//trim(varname)//' in any input files.') endif ; endif end subroutine read_param_char_array @@ -775,15 +811,14 @@ subroutine read_param_logical(CS, varname, value, fail_if_missing) !! if this variable is not found in the parameter file ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) + character(len=CS%max_line_len) :: value_string(1) logical :: found, defined call get_variable_line(CS, varname, found, defined, value_string, paramIsLogical=.true.) if (found) then value = defined elseif (present(fail_if_missing)) then ; if (fail_if_missing) then - call MOM_error(FATAL,'Unable to find variable '//trim(varname)// & - ' in any input files.') + call MOM_error(FATAL, 'Unable to find variable '//trim(varname)//' in any input files.') endif ; endif end subroutine read_param_logical @@ -802,7 +837,7 @@ subroutine read_param_time(CS, varname, value, timeunit, fail_if_missing, date_f !! later be logged in the same format. ! Local variables - character(len=INPUT_STR_LENGTH) :: value_string(1) + character(len=CS%max_line_len) :: value_string(1) character(len=240) :: err_msg logical :: found, defined real :: real_time, time_unit @@ -844,11 +879,9 @@ subroutine read_param_time(CS, varname, value, timeunit, fail_if_missing, date_f else if (present(fail_if_missing)) then ; if (fail_if_missing) then if (.not.found) then - call MOM_error(FATAL,'Unable to find variable '//trim(varname)// & - ' in any input files.') + call MOM_error(FATAL, 'Unable to find variable '//trim(varname)//' in any input files.') else - call MOM_error(FATAL,'Variable '//trim(varname)// & - ' found but not set in input files.') + call MOM_error(FATAL, 'Variable '//trim(varname)//' found but not set in input files.') endif endif ; endif endif @@ -860,8 +893,8 @@ end subroutine read_param_time !> This function removes single and double quotes from a character string function strip_quotes(val_str) - character(len=*) :: val_str !< The character string to work on - character(len=INPUT_STR_LENGTH) :: strip_quotes + character(len=*), intent(in) :: val_str !< The character string to work on + character(len=len(val_str)) :: strip_quotes ! Local variables integer :: i strip_quotes = val_str @@ -879,7 +912,68 @@ function strip_quotes(val_str) enddo end function strip_quotes -!> This subtoutine extracts the contents of lines in the param_file_type that refer to +!> This function returns the maximum number of characters in any input lines after they +!! have been combined by any line continuation. +function max_input_line_length(CS, pf_num) result(max_len) + type(param_file_type), intent(in) :: CS !< The control structure for the file_parser module, + !! it is also a structure to parse for run-time parameters + integer, optional, intent(in) :: pf_num !< If present, only work on a single file in the + !! param_file_type, or return 0 if this exceeds the + !! number of files in the param_file_type. + integer :: max_len !< The maximum number of characters in any input lines after they + !! have been combined by any line continuation. + + ! Local variables + character(len=FILENAME_LENGTH) :: filename + character :: last_char + integer :: ipf, ipf_s, ipf_e + integer :: last, last1, line_len, count, contBufSize + logical :: continuedLine + + max_len = 0 + ipf_s = 1 ; ipf_e = CS%nfiles + if (present(pf_num)) then + if (pf_num > CS%nfiles) return + ipf_s = pf_num ; ipf_e = pf_num + endif + + paramfile_loop: do ipf = ipf_s, ipf_e + filename = CS%filename(ipf) + contBufSize = 0 + continuedLine = .false. + + ! Scan through each line of the file + do count = 1, CS%param_data(ipf)%num_lines + ! line = CS%param_data(ipf)%fln(count)%line + last = len_trim(CS%param_data(ipf)%fln(count)%line) + last_char = " " + if (last > 0) last_char = CS%param_data(ipf)%fln(count)%line(last:last) + ! Check if line ends in continuation character (either & or \) + ! Note achar(92) is a backslash + if (last_char == achar(92) .or. last_char == "&") then + contBufSize = contBufSize + last - 1 + continuedLine = .true. + if (count==CS%param_data(ipf)%num_lines .and. is_root_pe()) & + call MOM_error(FATAL, "MOM_file_parser : the last line of the file ends in a"// & + " continuation character but there are no more lines to read. "// & + " Line: '"//trim(CS%param_data(ipf)%fln(count)%line(:last))//"'"// & + " in file "//trim(filename)//".") + cycle ! cycle inorder to append the next line of the file + elseif (continuedLine) then + ! If we reached this point then this is the end of line continuation + line_len = contBufSize + last + contBufSize = 0 + continuedLine = .false. + else ! This is a simple line with no continuation. + line_len = last + endif + max_len = max(max_len, line_len) + enddo ! CS%param_data(ipf)%num_lines + enddo paramfile_loop + +end function max_input_line_length + +!> This subroutine extracts the contents of lines in the param_file_type that refer to !! a named parameter. The value_string that is returned must be interepreted in a way !! that depends on the type of this variable. subroutine get_variable_line(CS, varname, found, defined, value_string, paramIsLogical) @@ -893,9 +987,10 @@ subroutine get_variable_line(CS, varname, found, defined, value_string, paramIsL !! that can be simply defined without parsing a value_string. ! Local variables - character(len=INPUT_STR_LENGTH) :: val_str, lname, origLine - character(len=INPUT_STR_LENGTH) :: line, continuationBuffer, blockName - character(len=FILENAME_LENGTH) :: filename + character(len=CS%max_line_len) :: val_str, lname, origLine + character(len=CS%max_line_len) :: line, continuationBuffer + character(len=240) :: blockName + character(len=FILENAME_LENGTH) :: filename integer :: is, id, isd, isu, ise, iso, ipf integer :: last, last1, ival, oval, max_vals, count, contBufSize character(len=52) :: set @@ -907,7 +1002,7 @@ subroutine get_variable_line(CS, varname, found, defined, value_string, paramIsL logical, parameter :: requireNamedClose = .false. integer, parameter :: verbose = 1 set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - continuationBuffer = repeat(" ",INPUT_STR_LENGTH) + continuationBuffer = repeat(" ", CS%max_line_len) contBufSize = 0 variableKindIsLogical=.false. @@ -928,7 +1023,7 @@ subroutine get_variable_line(CS, varname, found, defined, value_string, paramIsL ! Scan through each line of the file do count = 1, CS%param_data(ipf)%num_lines - line = CS%param_data(ipf)%line(count) + line = CS%param_data(ipf)%fln(count)%line last = len_trim(line) last1 = max(1,last) @@ -949,7 +1044,7 @@ subroutine get_variable_line(CS, varname, found, defined, value_string, paramIsL ! If we reached this point then this is the end of line continuation continuationBuffer(contBufSize+1:contBufSize+len_trim(line))=line(:last) line = continuationBuffer - continuationBuffer=repeat(" ",INPUT_STR_LENGTH) ! Clear for next use + continuationBuffer=repeat(" ",CS%max_line_len) ! Clear for next use contBufSize = 0 continuedLine = .false. last = len_trim(line) @@ -1173,8 +1268,8 @@ end subroutine get_variable_line !> Record that a line has been used to set a parameter subroutine flag_line_as_read(line_used, count) - logical, dimension(:), pointer :: line_used !< A structure indicating which lines have been read - integer, intent(in) :: count !< The parameter on this line number has been read + logical, dimension(:), pointer :: line_used !< A structure indicating which lines have been read + integer, intent(in) :: count !< The parameter on this line number has been read line_used(count) = .true. end subroutine flag_line_as_read @@ -1298,7 +1393,7 @@ subroutine log_param_int_array(CS, modulename, varname, value, desc, & logical, optional, intent(in) :: like_default !< If present and true, log this parameter as !! though it has the default value, even if there is no default. - character(len=1320) :: mesg + character(len=CS%max_line_len+120) :: mesg character(len=240) :: myunits write(mesg, '(" ",a," ",a,": ",A)') trim(modulename), trim(varname), trim(left_ints(value)) @@ -1440,16 +1535,16 @@ subroutine log_param_char(CS, modulename, varname, value, desc, units, & logical, optional, intent(in) :: like_default !< If present and true, log this parameter as !! though it has the default value, even if there is no default. - character(len=1024) :: mesg, myunits + character(len=:), allocatable :: mesg + character(len=240) :: myunits - write(mesg, '(" ",a," ",a,": ",a)') & - trim(modulename), trim(varname), trim(value) + mesg = " " // trim(modulename) // " " // trim(varname) // ": " // trim(value) if (is_root_pe()) then if (CS%log_open) write(CS%stdlog,'(a)') trim(mesg) if (CS%log_to_stdout) write(CS%stdout,'(a)') trim(mesg) endif - myunits=" "; if (present(units)) write(myunits(1:1024),'(A)') trim(units) + myunits=" "; if (present(units)) write(myunits(1:240),'(A)') trim(units) if (present(desc)) & call doc_param(CS%doc, varname, desc, myunits, value, default, & layoutParam=layoutParam, debuggingParam=debuggingParam, like_default=like_default) @@ -1827,7 +1922,7 @@ subroutine get_param_char_array(CS, modulename, varname, value, desc, units, & ! Local variables logical :: do_read, do_log integer :: i, len_tot, len_val - character(len=1024) :: cat_val + character(len=:), allocatable :: cat_val do_read = .true. ; if (present(do_not_read)) do_read = .not.do_not_read do_log = .true. ; if (present(do_not_log)) do_log = .not.do_not_log @@ -1838,7 +1933,7 @@ subroutine get_param_char_array(CS, modulename, varname, value, desc, units, & endif if (do_log) then - cat_val = trim(value(1)); len_tot = len_trim(value(1)) + cat_val = trim(value(1)) ; len_tot = len_trim(value(1)) do i=2,size(value) len_val = len_trim(value(i)) if ((len_val > 0) .and. (len_tot + len_val + 2 < 240)) then