← back to index

src/framework/MOM_string_functions.F90

portedportable, not yet portedexecuted, not portableexecutable, not hit by this run

1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> Handy functions for manipulating strings
6module MOM_string_functions
7
8use iso_fortran_env, only : stdout=>output_unit, stderr=>error_unit
9
10implicit none ; private
11
12public lowercase, uppercase
13public left_int, left_ints
14public left_real, left_reals
15public string_functions_unit_tests
16public extractWord
17public extract_word
18public extract_integer
19public extract_real
20public remove_spaces
21public slasher
22public trim_trailing_commas
23public ints_to_string
24
25contains
26
27!> Return a string in which all uppercase letters have been replaced by
28!! their lowercase counterparts.
2943function lowercase(input_string)
30 character(len=*), intent(in) :: input_string !< The string to modify
31 character(len=len(input_string)) :: lowercase !< The modified output string
32! This function returns a string in which all uppercase letters have been
33! replaced by their lowercase counterparts. It is loosely based on the
34! lowercase function in mpp_util.F90.
35 integer, parameter :: co=iachar('a')-iachar('A') ! case offset
36 integer :: k
37
3843 lowercase = input_string
391226 do k=1, len_trim(input_string)
401183 if (lowercase(k:k) >= 'A' .and. lowercase(k:k) <= 'Z') &
41146 lowercase(k:k) = achar(ichar(lowercase(k:k))+co)
42 enddo
4343end function lowercase
44
45!> Return a string in which all uppercase letters have been replaced by
46!! their lowercase counterparts.
47633function uppercase(input_string)
48 character(len=*), intent(in) :: input_string !< The string to modify
49 character(len=len(input_string)) :: uppercase !< The modified output string
50! This function returns a string in which all lowercase letters have been
51! replaced by their uppercase counterparts. It is loosely based on the
52! uppercase function in mpp_util.F90.
53 integer, parameter :: co=iachar('A')-iachar('a') ! case offset
54 integer :: k
55
56633 uppercase = input_string
573839 do k=1, len_trim(input_string)
583206 if (uppercase(k:k) >= 'a' .and. uppercase(k:k) <= 'z') &
59639 uppercase(k:k) = achar(ichar(uppercase(k:k))+co)
60 enddo
61633end function uppercase
62
63!> Returns a character string of a left-formatted integer
64!! e.g. "123 " (assumes 19 digit maximum)
6591function left_int(i)
66 integer, intent(in) :: i !< The integer to convert to a string
67 character(len=19) :: left_int !< The output string
68
69 character(len=19) :: tmp
7091 write(tmp(1:19),'(I19)') i
7191 write(left_int(1:19),'(A)') adjustl(tmp)
7291end function left_int
73
74!> Returns a character string of a comma-separated, compact formatted,
75!! integers e.g. "1, 2, 3, 4"
762function left_ints(i)
77 integer, intent(in) :: i(:) !< The array of integers to convert to a string
78 character(len=1320) :: left_ints !< The output string
79
80 character(len=1320) :: tmp
81 integer :: j
822 write(left_ints(1:1320),'(A)') trim(left_int(i(1)))
832 if (size(i)>1) then
844 do j=2,size(i)
852 tmp=left_ints
864 write(left_ints(1:1320),'(A,", ",A)') trim(tmp),trim(left_int(i(j)))
87 enddo
88 endif
892end function left_ints
90
91!> Returns a left-justified string with a real formatted like '(G)'
92376function left_real(val)
93 real, intent(in) :: val !< The real variable to convert to a string, in arbitrary units [A]
94 character(len=32) :: left_real !< The output string
95
96 integer :: l, ind
97
98376 if ((abs(val) < 1.0e4) .and. (abs(val) >= 1.0e-3)) then
99249 write(left_real, '(F30.11)') val
100249 if (.not.isFormattedFloatEqualTo(left_real,val)) then
10123 write(left_real, '(F30.12)') val
10223 if (.not.isFormattedFloatEqualTo(left_real,val)) then
10323 write(left_real, '(F30.13)') val
10423 if (.not.isFormattedFloatEqualTo(left_real,val)) then
10521 write(left_real, '(F30.14)') val
10621 if (.not.isFormattedFloatEqualTo(left_real,val)) then
1079 write(left_real, '(F30.15)') val
1089 if (.not.isFormattedFloatEqualTo(left_real,val)) then
1090 write(left_real, '(F30.16)') val
110 endif
111 endif
112 endif
113 endif
114 endif
1152185 do
1162434 l = len_trim(left_real)
1172434 if ((l<2) .or. (left_real(l-1:l) == ".0") .or. &
118249 (left_real(l:l) /= "0")) exit
1192185 left_real(l:l) = " "
120 enddo
121127 elseif (val == 0.) then
12288 left_real = "0.0"
123 else
12439 if ((abs(val) <= 1.0e-100) .or. (abs(val) >= 1.0e100)) then
1250 write(left_real(1:32), '(ES24.14E3)') val
1260 if (.not.isFormattedFloatEqualTo(left_real,val)) &
1270 write(left_real(1:32), '(ES24.15E3)') val
128 else
12939 write(left_real(1:32), '(ES23.14)') val
13039 if (.not.isFormattedFloatEqualTo(left_real,val)) &
1312 write(left_real(1:32), '(ES23.15)') val
132 endif
133482 do
134521 ind = index(left_real,"0E")
135521 if (ind == 0) exit
136498 if (left_real(ind-1:ind-1) == ".") exit
137482 left_real = left_real(1:ind-1)//left_real(ind+1:)
138 enddo
139 endif
140376 left_real = adjustl(left_real)
141376end function left_real
142
143!> Returns a character string of a comma-separated, compact formatted, reals
144!! e.g. "1., 2., 5*3., 5.E2"
1453function left_reals(r,sep)
146 real, intent(in) :: r(:) !< The array of real variables to convert to a string, in arbitrary units [A]
147 character(len=*), optional, intent(in) :: sep !< The separator between
148 !! successive values, by default it is ', '.
149 character(len=:), allocatable :: left_reals !< The output string
150
151 integer :: j, n, ns
152 logical :: doWrite
153 character(len=10) :: separator
154
1553 n=1 ; doWrite=.true. ; left_reals=''
1563 if (present(sep)) then
1570 separator=sep ; ns=len(sep)
158 else
1593 separator=', ' ; ns=2
160 endif
16184 do j=1,size(r)
16281 doWrite=.true.
16381 if (j<size(r)) then
16478 if (r(j)==r(j+1)) then
1650 n=n+1
1660 doWrite=.false.
167 endif
168 endif
16984 if (doWrite) then
17081 if (len(left_reals)>0) then ! Write separator if a number has already been written
17178 left_reals = left_reals // separator(1:ns)
172 endif
17381 if (n>1) then
1740 left_reals = left_reals // trim(left_int(n)) // "*" // trim(left_real(r(j)))
175 else
17681 left_reals = left_reals // trim(left_real(r(j)))
177 endif
17881 n=1
179 endif
180 enddo
1813end function left_reals
182
183!> Returns True if the string can be read/parsed to give the exact value of "val"
184364function isFormattedFloatEqualTo(str, val)
185 character(len=*), intent(in) :: str !< The string to parse
186 real, intent(in) :: val !< The real value to compare with, in arbitrary units [A]
187 logical :: isFormattedFloatEqualTo
188 ! Local variables
189 real :: scannedVal ! The value extraced from str, in arbitrary units [A]
190
191364 isFormattedFloatEqualTo=.false.
192364 read(str(1:),*,err=987) scannedVal
193364 if (scannedVal == val) isFormattedFloatEqualTo=.true.
194364 987 return
195364end function isFormattedFloatEqualTo
196
197!> Returns the string corresponding to the nth word in the argument
198!! or "" if the string is not long enough. Both spaces and commas
199!! are interpreted as separators.
2003character(len=120) function extractWord(string, n)
201 character(len=*), intent(in) :: string !< The string to scan
202 integer, intent(in) :: n !< Number of word to extract
203
2046 extractWord = extract_word(string, ' ,', n)
205
2063end function extractWord
207
208!> Returns the string corresponding to the nth word in the argument
209!! or "" if the string is not long enough. Words are delineated
210!! by the mandatory separators argument.
2113character(len=120) function extract_word(string, separators, n)
212 character(len=*), intent(in) :: string !< String to scan
213 character(len=*), intent(in) :: separators !< Characters to use for delineation
214 integer, intent(in) :: n !< Number of word to extract
215 ! Local variables
216 integer :: ns, i, b, e, nw
217 logical :: lastCharIsSeperator
2183 extract_word = ''
2193 lastCharIsSeperator = .true.
2203 ns = len_trim(string)
2213 i = 0 ; b=0 ; e=0 ; nw=0
22216 do while (i<ns)
22315 i = i+1
22415 if (lastCharIsSeperator) then ! search for end of word
2256 if (verify(string(i:i),separators)==0) then
226 continue ! Multiple separators
227 else
2286 lastCharIsSeperator = .false. ! character is beginning of word
2296 b = i
230 continue
231 endif
232 else ! continue search for end of word
2339 if (verify(string(i:i),separators)==0) then
2345 lastCharIsSeperator = .true.
2355 e = i-1 ! Previous character is end of word
2365 nw = nw+1
2375 if (nw==n) then
2382 extract_word = trim(string(b:e))
2392 return
240 endif
241 endif
242 endif
243 enddo
2441 if (b<=ns .and. nw==n-1) extract_word = trim(string(b:ns))
2453end function extract_word
246
247!> Returns the integer corresponding to the nth word in the argument.
2480integer function extract_integer(string, separators, n, missing_value)
249 character(len=*), intent(in) :: string !< String to scan
250 character(len=*), intent(in) :: separators !< Characters to use for delineation
251 integer, intent(in) :: n !< Number of word to extract
252 integer, optional, intent(in) :: missing_value !< Value to assign if word is missing
253 ! Local variables
254 character(len=20) :: word
255
2560 word = extract_word(string, separators, n)
257
2580 if (len_trim(word)>0) then
2590 read(word(1:len_trim(word)),*) extract_integer
260 else
2610 if (present(missing_value)) then
2620 extract_integer = missing_value
263 else
2640 extract_integer = 0
265 endif
266 endif
267
2680end function extract_integer
269
270!> Returns the real corresponding to the nth word in the argument, in arbitrary units [A].
2710real function extract_real(string, separators, n, missing_value)
272 character(len=*), intent(in) :: string !< String to scan
273 character(len=*), intent(in) :: separators !< Characters to use for delineation
274 integer, intent(in) :: n !< Number of word to extract
275 real, optional, intent(in) :: missing_value !< Value to assign if word is missing, in arbitrary units [A]
276 ! Local variables
277 character(len=20) :: word
278
2790 word = extract_word(string, separators, n)
280
2810 if (len_trim(word)>0) then
2820 read(word(1:len_trim(word)),*) extract_real
283 else
2840 if (present(missing_value)) then
2850 extract_real = missing_value
286 else
2870 extract_real = 0
288 endif
289 endif
290
2910end function extract_real
292
293!> Returns string with all spaces removed.
2940character(len=120) function remove_spaces(string)
295 character(len=*), intent(in) :: string !< String to scan
296 ! Local variables
297 integer :: ns, i, o
298 logical :: lastCharIsSeperator
2990 lastCharIsSeperator = .true.
3000 ns = len_trim(string)
3010 i = 0 ; o = 0
3020 do while (i<ns)
3030 i = i+1
3040 if (string(i:i) /= ' ') then ! Copy character to output string
3050 o = o + 1
3060 remove_spaces(o:o) = string(i:i)
307 endif
308 enddo
3090 do i = o+1, 120
3100 remove_spaces(i:i) = ' ' ! Wipe any non-empty characters
311 enddo
3120 remove_spaces = trim(remove_spaces)
3130end function remove_spaces
314
315!> Returns true if a unit test of string_functions fails.
3160logical function string_functions_unit_tests(verbose)
317 ! Arguments
318 logical, intent(in) :: verbose !< If true, write results to stdout
319 ! Local variables
320 integer :: i(5) = (/ -1, 1, 3, 3, 0 /)
321 ! This is an array of real test values, in arbitrary units [A]
322 real :: r(8) = (/ 0., 1., -2., 1.3, 3.E-11, 3.E-11, 3.E-11, -5.1E12 /)
323 logical :: fail, v
3240 fail = .false.
3250 v = verbose
3260 write(stdout,*) '==== MOM_string_functions: string_functions_unit_tests ==='
3270 fail = fail .or. localTestS(v,left_int(-1),'-1')
3280 fail = fail .or. localTestS(v,left_ints(i(:)),'-1, 1, 3, 3, 0')
3290 fail = fail .or. localTestS(v,left_real(0.),'0.0')
3300 fail = fail .or. localTestS(v,left_reals(r(:)),'0.0, 1.0, -2.0, 1.3, 3*3.0E-11, -5.1E+12')
3310 fail = fail .or. localTestS(v,left_reals(r(:),sep=' '),'0.0 1.0 -2.0 1.3 3*3.0E-11 -5.1E+12')
3320 fail = fail .or. localTestS(v,left_reals(r(:),sep=','),'0.0,1.0,-2.0,1.3,3*3.0E-11,-5.1E+12')
3330 fail = fail .or. localTestS(v,ints_to_string(i(:),5),'_-0001_0001_0003_0003_0000')
3340 fail = fail .or. localTestS(v,ints_to_string(i(2:),2),'_0001_0003')
3350 fail = fail .or. localTestS(v,ints_to_string(i(:)),'_-0001_0001_0003')
3360 fail = fail .or. localTestS(v,trim_trailing_commas("One, Two, Three, "), "One, Two, Three")
3370 fail = fail .or. localTestS(v,extractWord("One Two,Three",1),"One")
3380 fail = fail .or. localTestS(v,extractWord("One Two,Three",2),"Two")
3390 fail = fail .or. localTestS(v,extractWord("One Two,Three",3),"Three")
3400 fail = fail .or. localTestS(v,extractWord("One Two, Three",3),"Three")
3410 fail = fail .or. localTestS(v,extractWord(" One Two,Three",1),"One")
3420 fail = fail .or. localTestS(v,extract_word("One,Two,Three",",",3),"Three")
3430 fail = fail .or. localTestS(v,extract_word("One,Two,Three",",",4),"")
3440 fail = fail .or. localTestS(v,remove_spaces("1 2 3"),"123")
3450 fail = fail .or. localTestS(v,remove_spaces(" 1 2 3"),"123")
3460 fail = fail .or. localTestS(v,remove_spaces("1 2 3 "),"123")
3470 fail = fail .or. localTestS(v,remove_spaces("123"),"123")
3480 fail = fail .or. localTestS(v,remove_spaces(" "),"")
3490 fail = fail .or. localTestS(v,remove_spaces(""),"")
3500 fail = fail .or. localTestI(v,extract_integer("1","",1),1)
3510 fail = fail .or. localTestI(v,extract_integer("1,2,3",",",1),1)
3520 fail = fail .or. localTestI(v,extract_integer("1,2",",",2),2)
3530 fail = fail .or. localTestI(v,extract_integer("1,2",",",3),0)
3540 fail = fail .or. localTestI(v,extract_integer("1,2",",",4,4),4)
3550 fail = fail .or. localTestR(v,extract_real("1.","",1),1.)
3560 fail = fail .or. localTestR(v,extract_real("1.,2.,3.",",",1),1.)
3570 fail = fail .or. localTestR(v,extract_real("1.,2.",",",2),2.)
3580 fail = fail .or. localTestR(v,extract_real("1.,2.",",",3),0.)
3590 fail = fail .or. localTestR(v,extract_real("1.,2.",",",4,4.),4.)
3600 if (.not. fail) write(stdout,*) 'Pass'
3610 string_functions_unit_tests = fail
3620end function string_functions_unit_tests
363
364!> True if str1 does not match str2. False otherwise.
3650logical function localTestS(verbose,str1,str2)
366 logical, intent(in) :: verbose !< If true, write results to stdout
367 character(len=*), intent(in) :: str1 !< String
368 character(len=*), intent(in) :: str2 !< String
3690 localTestS=.false.
3700 if (trim(str1)/=trim(str2)) localTestS=.true.
3710 if (localTestS .or. verbose) then
3720 write(stdout,*) '>'//trim(str1)//'<'
3730 if (localTestS) then
3740 write(stdout,*) trim(str1),':',trim(str2), '<-- FAIL'
3750 write(stderr,*) trim(str1),':',trim(str2), '<-- FAIL'
376 endif
377 endif
3780end function localTestS
379
380!> True if i1 is not equal to i2. False otherwise.
3810logical function localTestI(verbose,i1,i2)
382 logical, intent(in) :: verbose !< If true, write results to stdout
383 integer, intent(in) :: i1 !< Integer
384 integer, intent(in) :: i2 !< Integer
3850 localTestI=.false.
3860 if (i1/=i2) localTestI=.true.
3870 if (localTestI .or. verbose) then
3880 write(stdout,*) i1,i2
3890 if (localTestI) then
3900 write(stdout,*) i1,'!=',i2, '<-- FAIL'
3910 write(stderr,*) i1,'!=',i2, '<-- FAIL'
392 endif
393 endif
3940end function localTestI
395
396!> True if r1 is not equal to r2. False otherwise.
3970logical function localTestR(verbose,r1,r2)
398 logical, intent(in) :: verbose !< If true, write results to stdout
399 real, intent(in) :: r1 !< The first value to compare, in arbitrary units [A]
400 real, intent(in) :: r2 !< The first value to compare, in arbitrary units [A]
4010 localTestR=.false.
4020 if (r1/=r2) localTestR=.true.
4030 if (localTestR .or. verbose) then
4040 write(stdout,*) r1,r2
4050 if (localTestR) then
4060 write(stdout,*) r1,'!=',r2, '<-- FAIL'
4070 write(stderr,*) r1,'!=',r2, '<-- FAIL'
408 endif
409 endif
4100end function localTestR
411
412!> Returns a directory name that is terminated with a "/" or "./" if the
413!! argument is an empty string.
41418function slasher(dir)
415 character(len=*), intent(in) :: dir !< A directory to be terminated with a "/"
416 !! or changed to "./" if it is blank.
417 character(len=len(dir)+2) :: slasher
418
41918 if (len_trim(dir) == 0) then
4200 slasher = "./"
42118 elseif (dir(len_trim(dir):len_trim(dir)) == '/') then
42211 slasher = trim(dir)
423 else
4247 slasher = trim(dir)//"/"
425 endif
42618end function slasher
427
428!> Returns a left-adjusted string with trailing blanks and commas removed.
429688function trim_trailing_commas(in_str) result(out_str)
430 character(len=*), intent(in) :: in_str !< A string that is to be left adjusted and have
431 !! its trailing commas and white space removed.
432 character(len=len(in_str)) :: out_str !< A left-adjusted version of in_str with
433 !! trailing commas and white space removed
434
435688 out_str = trim(adjustl(in_str))
436688 if (len_trim(out_str) > 0) then
437688 if (out_str(len_trim(out_str):len_trim(out_str)) == ",") then
438688 out_str = out_str(1:len_trim(out_str) - 1)
439 endif
440688 out_str = trim(out_str)
441 endif
442
443688end function trim_trailing_commas
444
445!> Convert the first n elements (3 by default) of an integer array into an underscore delimited string.
44645function ints_to_string(a, n) result(i2s)
447 integer, dimension(:), intent(in) :: a !< The array of integers to translate
448 integer, optional , intent(in) :: n !< The number of elements to translate, by default the lesser
449 !! of 3 or all of the integers
450 character(len=5*size(a)+1) :: i2s !< The returned underscore delimited string of integers
451
452 character(len=8) :: i2s_temp
453 integer :: i, n_max
454
45545 n_max = 3
45645 if (present(n)) n_max = n
457
45845 i2s = ''
459162 do i=1,min(size(a), n_max)
460117 if (a(i) < 0) then
4610 write (i2s_temp, '(I5.4)') a(i)
462 else
463117 write (i2s_temp, '(I4.4)') a(i)
464 endif
465162 i2s = trim(i2s) //'_'// trim(i2s_temp)
466 enddo
46745 i2s = adjustl(i2s)
46845end function ints_to_string
469
470
471!> \namespace mom_string_functions
472!!
473!! By Alistair Adcroft and Robert Hallberg, last updated Sept. 2013.
474!!
475!! The functions here perform a set of useful manipulations of
476!! character strings. Although they are a part of MOM6, the do not
477!! require any other MOM software to be useful.
478
479159end module MOM_string_functions