MOM6
MOM_string_functions.F90
1 !> Handy functions for manipulating strings
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 use iso_fortran_env, only : stdout=>output_unit, stderr=>error_unit
7 
8 implicit none ; private
9 
10 public lowercase, uppercase
11 public left_int, left_ints
12 public left_real, left_reals
13 public string_functions_unit_tests
14 public extractword
15 public extract_word
16 public extract_integer
17 public extract_real
18 public remove_spaces
19 public slasher
20 
21 contains
22 
23 !> Return a string in which all uppercase letters have been replaced by
24 !! their lowercase counterparts.
25 function lowercase(input_string)
26  character(len=*), intent(in) :: input_string !< The string to modify
27  character(len=len(input_string)) :: lowercase !< The modified output string
28 ! This function returns a string in which all uppercase letters have been
29 ! replaced by their lowercase counterparts. It is loosely based on the
30 ! lowercase function in mpp_util.F90.
31  integer, parameter :: co=iachar('a')-iachar('A') ! case offset
32  integer :: k
33 
34  lowercase = input_string
35  do k=1, len_trim(input_string)
36  if (lowercase(k:k) >= 'A' .and. lowercase(k:k) <= 'Z') &
37  lowercase(k:k) = achar(ichar(lowercase(k:k))+co)
38  enddo
39 end function lowercase
40 
41 !> Return a string in which all uppercase letters have been replaced by
42 !! their lowercase counterparts.
43 function uppercase(input_string)
44  character(len=*), intent(in) :: input_string !< The string to modify
45  character(len=len(input_string)) :: uppercase !< The modified output string
46 ! This function returns a string in which all lowercase letters have been
47 ! replaced by their uppercase counterparts. It is loosely based on the
48 ! uppercase function in mpp_util.F90.
49  integer, parameter :: co=iachar('A')-iachar('a') ! case offset
50  integer :: k
51 
52  uppercase = input_string
53  do k=1, len_trim(input_string)
54  if (uppercase(k:k) >= 'a' .and. uppercase(k:k) <= 'z') &
55  uppercase(k:k) = achar(ichar(uppercase(k:k))+co)
56  enddo
57 end function uppercase
58 
59 !> Returns a character string of a left-formatted integer
60 !! e.g. "123 " (assumes 19 digit maximum)
61 function left_int(i)
62  integer, intent(in) :: i !< The integer to convert to a string
63  character(len=19) :: left_int !< The output string
64 
65  character(len=19) :: tmp
66  write(tmp(1:19),'(I19)') i
67  write(left_int(1:19),'(A)') adjustl(tmp)
68 end function left_int
69 
70 !> Returns a character string of a comma-separated, compact formatted,
71 !! integers e.g. "1, 2, 3, 4"
72 function left_ints(i)
73  integer, intent(in) :: i(:) !< The array of integers to convert to a string
74  character(len=1320) :: left_ints !< The output string
75 
76  character(len=1320) :: tmp
77  integer :: j
78  write(left_ints(1:1320),'(A)') trim(left_int(i(1)))
79  if (size(i)>1) then
80  do j=2,size(i)
81  tmp=left_ints
82  write(left_ints(1:1320),'(A,", ",A)') trim(tmp),trim(left_int(i(j)))
83  enddo
84  endif
85 end function left_ints
86 
87 !> Returns a left-justified string with a real formatted like '(G)'
88 function left_real(val)
89  real, intent(in) :: val !< The real variable to convert to a string
90  character(len=32) :: left_real !< The output string
91 
92  integer :: l, ind
93 
94  if ((abs(val) < 1.0e4) .and. (abs(val) >= 1.0e-3)) then
95  write(left_real, '(F30.11)') val
96  if (.not.isformattedfloatequalto(left_real,val)) then
97  write(left_real, '(F30.12)') val
98  if (.not.isformattedfloatequalto(left_real,val)) then
99  write(left_real, '(F30.13)') val
100  if (.not.isformattedfloatequalto(left_real,val)) then
101  write(left_real, '(F30.14)') val
102  if (.not.isformattedfloatequalto(left_real,val)) then
103  write(left_real, '(F30.15)') val
104  if (.not.isformattedfloatequalto(left_real,val)) then
105  write(left_real, '(F30.16)') val
106  endif
107  endif
108  endif
109  endif
110  endif
111  do
112  l = len_trim(left_real)
113  if ((l<2) .or. (left_real(l-1:l) == ".0") .or. &
114  (left_real(l:l) /= "0")) exit
115  left_real(l:l) = " "
116  enddo
117  elseif (val == 0.) then
118  left_real = "0.0"
119  else
120  if ((abs(val) <= 1.0e-100) .or. (abs(val) >= 1.0e100)) then
121  write(left_real(1:32), '(ES24.14E3)') val
122  if (.not.isformattedfloatequalto(left_real,val)) &
123  write(left_real(1:32), '(ES24.15E3)') val
124  else
125  write(left_real(1:32), '(ES23.14)') val
126  if (.not.isformattedfloatequalto(left_real,val)) &
127  write(left_real(1:32), '(ES23.15)') val
128  endif
129  do
130  ind = index(left_real,"0E")
131  if (ind == 0) exit
132  if (left_real(ind-1:ind-1) == ".") exit
133  left_real = left_real(1:ind-1)//left_real(ind+1:)
134  enddo
135  endif
136  left_real = adjustl(left_real)
137 end function left_real
138 
139 !> Returns a character string of a comma-separated, compact formatted, reals
140 !! e.g. "1., 2., 5*3., 5.E2"
141 function left_reals(r,sep)
142  real, intent(in) :: r(:) !< The array of real variables to convert to a string
143  character(len=*), optional, intent(in) :: sep !< The separator between
144  !! successive values, by default it is ', '.
145  character(len=1320) :: left_reals !< The output string
146 
147  integer :: j, n, b, ns
148  logical :: dowrite
149  character(len=10) :: separator
150 
151  n=1 ; dowrite=.true. ; left_reals='' ; b=1
152  if (present(sep)) then
153  separator=sep ; ns=len(sep)
154  else
155  separator=', ' ; ns=2
156  endif
157  do j=1,size(r)
158  dowrite=.true.
159  if (j<size(r)) then
160  if (r(j)==r(j+1)) then
161  n=n+1
162  dowrite=.false.
163  endif
164  endif
165  if (dowrite) then
166  if (b>1) then ! Write separator if a number has already been written
167  write(left_reals(b:),'(A)') separator
168  b=b+ns
169  endif
170  if (n>1) then
171  write(left_reals(b:),'(A,"*",A)') trim(left_int(n)),trim(left_real(r(j)))
172  else
173  write(left_reals(b:),'(A)') trim(left_real(r(j)))
174  endif
175  n=1 ; b=len_trim(left_reals)+1
176  endif
177  enddo
178 end function left_reals
179 
180 !> Returns True if the string can be read/parsed to give the exact value of "val"
181 function isformattedfloatequalto(str, val)
182  character(len=*), intent(in) :: str !< The string to parse
183  real, intent(in) :: val !< The real value to compare with
184  logical :: isformattedfloatequalto
185  ! Local variables
186  real :: scannedval
187 
188  isformattedfloatequalto=.false.
189  read(str(1:),*,err=987) scannedval
190  if (scannedval == val) isformattedfloatequalto=.true.
191  987 return
192 end function isformattedfloatequalto
193 
194 !> Returns the string corresponding to the nth word in the argument
195 !! or "" if the string is not long enough. Both spaces and commas
196 !! are interpreted as separators.
197 character(len=120) function extractword(string, n)
198  character(len=*), intent(in) :: string !< The string to scan
199  integer, intent(in) :: n !< Number of word to extract
200 
201  extractword = extract_word(string, ' ,', n)
202 
203 end function extractword
204 
205 !> Returns the string corresponding to the nth word in the argument
206 !! or "" if the string is not long enough. Words are delineated
207 !! by the mandatory separators argument.
208 character(len=120) function extract_word(string, separators, n)
209  character(len=*), intent(in) :: string !< String to scan
210  character(len=*), intent(in) :: separators !< Characters to use for delineation
211  integer, intent(in) :: n !< Number of word to extract
212  ! Local variables
213  integer :: ns, i, b, e, nw
214  logical :: lastcharisseperator
215  extract_word = ''
216  lastcharisseperator = .true.
217  ns = len_trim(string)
218  i = 0; b=0; e=0; nw=0
219  do while (i<ns)
220  i = i+1
221  if (lastcharisseperator) then ! search for end of word
222  if (verify(string(i:i),separators)==0) then
223  continue ! Multiple separators
224  else
225  lastcharisseperator = .false. ! character is beginning of word
226  b = i
227  continue
228  endif
229  else ! continue search for end of word
230  if (verify(string(i:i),separators)==0) then
231  lastcharisseperator = .true.
232  e = i-1 ! Previous character is end of word
233  nw = nw+1
234  if (nw==n) then
235  extract_word = trim(string(b:e))
236  return
237  endif
238  endif
239  endif
240  enddo
241  if (b<=ns .and. nw==n-1) extract_word = trim(string(b:ns))
242 end function extract_word
243 
244 !> Returns the integer corresponding to the nth word in the argument.
245 integer function extract_integer(string, separators, n, missing_value)
246  character(len=*), intent(in) :: string !< String to scan
247  character(len=*), intent(in) :: separators !< Characters to use for delineation
248  integer, intent(in) :: n !< Number of word to extract
249  integer, optional, intent(in) :: missing_value !< Value to assign if word is missing
250  ! Local variables
251  integer :: ns, i, b, e, nw
252  character(len=20) :: word
253 
254  word = extract_word(string, separators, n)
255 
256  if (len_trim(word)>0) then
257  read(word(1:len_trim(word)),*) extract_integer
258  else
259  if (present(missing_value)) then
260  extract_integer = missing_value
261  else
262  extract_integer = 0
263  endif
264  endif
265 
266 end function extract_integer
267 
268 !> Returns the real corresponding to the nth word in the argument.
269 real function extract_real(string, separators, n, missing_value)
270  character(len=*), intent(in) :: string !< String to scan
271  character(len=*), intent(in) :: separators !< Characters to use for delineation
272  integer, intent(in) :: n !< Number of word to extract
273  real, optional, intent(in) :: missing_value !< Value to assign if word is missing
274  ! Local variables
275  integer :: ns, i, b, e, nw
276  character(len=20) :: word
277 
278  word = extract_word(string, separators, n)
279 
280  if (len_trim(word)>0) then
281  read(word(1:len_trim(word)),*) extract_real
282  else
283  if (present(missing_value)) then
284  extract_real = missing_value
285  else
286  extract_real = 0
287  endif
288  endif
289 
290 end function extract_real
291 
292 !> Returns string with all spaces removed.
293 character(len=120) function remove_spaces(string)
294  character(len=*), intent(in) :: string !< String to scan
295  ! Local variables
296  integer :: ns, i, o
297  logical :: lastcharisseperator
298  lastcharisseperator = .true.
299  ns = len_trim(string)
300  i = 0; o = 0
301  do while (i<ns)
302  i = i+1
303  if (string(i:i) /= ' ') then ! Copy character to output string
304  o = o + 1
305  remove_spaces(o:o) = string(i:i)
306  endif
307  enddo
308  do i = o+1, 120
309  remove_spaces(i:i) = ' ' ! Wipe any non-empty characters
310  enddo
311  remove_spaces = trim(remove_spaces)
312 end function remove_spaces
313 
314 !> Returns true if a unit test of string_functions fails.
315 logical function string_functions_unit_tests(verbose)
316  ! Arguments
317  logical, intent(in) :: verbose !< If true, write results to stdout
318  ! Local variables
319  integer :: i(5) = (/ -1, 1, 3, 3, 0 /)
320  real :: r(8) = (/ 0., 1., -2., 1.3, 3.e-11, 3.e-11, 3.e-11, -5.1e12 /)
321  logical :: fail, v
322  fail = .false.
323  v = verbose
324  write(stdout,*) '==== MOM_string_functions: string_functions_unit_tests ==='
325  fail = fail .or. localtests(v,left_int(-1),'-1')
326  fail = fail .or. localtests(v,left_ints(i(:)),'-1, 1, 3, 3, 0')
327  fail = fail .or. localtests(v,left_real(0.),'0.0')
328  fail = fail .or. localtests(v,left_reals(r(:)),'0.0, 1.0, -2.0, 1.3, 3*3.0E-11, -5.1E+12')
329  fail = fail .or. localtests(v,left_reals(r(:),sep=' '),'0.0 1.0 -2.0 1.3 3*3.0E-11 -5.1E+12')
330  fail = fail .or. localtests(v,left_reals(r(:),sep=','),'0.0,1.0,-2.0,1.3,3*3.0E-11,-5.1E+12')
331  fail = fail .or. localtests(v,extractword("One Two,Three",1),"One")
332  fail = fail .or. localtests(v,extractword("One Two,Three",2),"Two")
333  fail = fail .or. localtests(v,extractword("One Two,Three",3),"Three")
334  fail = fail .or. localtests(v,extractword("One Two, Three",3),"Three")
335  fail = fail .or. localtests(v,extractword(" One Two,Three",1),"One")
336  fail = fail .or. localtests(v,extract_word("One,Two,Three",",",3),"Three")
337  fail = fail .or. localtests(v,extract_word("One,Two,Three",",",4),"")
338  fail = fail .or. localtests(v,remove_spaces("1 2 3"),"123")
339  fail = fail .or. localtests(v,remove_spaces(" 1 2 3"),"123")
340  fail = fail .or. localtests(v,remove_spaces("1 2 3 "),"123")
341  fail = fail .or. localtests(v,remove_spaces("123"),"123")
342  fail = fail .or. localtests(v,remove_spaces(" "),"")
343  fail = fail .or. localtests(v,remove_spaces(""),"")
344  fail = fail .or. localtesti(v,extract_integer("1","",1),1)
345  fail = fail .or. localtesti(v,extract_integer("1,2,3",",",1),1)
346  fail = fail .or. localtesti(v,extract_integer("1,2",",",2),2)
347  fail = fail .or. localtesti(v,extract_integer("1,2",",",3),0)
348  fail = fail .or. localtesti(v,extract_integer("1,2",",",4,4),4)
349  fail = fail .or. localtestr(v,extract_real("1.","",1),1.)
350  fail = fail .or. localtestr(v,extract_real("1.,2.,3.",",",1),1.)
351  fail = fail .or. localtestr(v,extract_real("1.,2.",",",2),2.)
352  fail = fail .or. localtestr(v,extract_real("1.,2.",",",3),0.)
353  fail = fail .or. localtestr(v,extract_real("1.,2.",",",4,4.),4.)
354  if (.not. fail) write(stdout,*) 'Pass'
355  string_functions_unit_tests = fail
356 end function string_functions_unit_tests
357 
358 !> True if str1 does not match str2. False otherwise.
359 logical function localtests(verbose,str1,str2)
360  logical, intent(in) :: verbose !< If true, write results to stdout
361  character(len=*), intent(in) :: str1 !< String
362  character(len=*), intent(in) :: str2 !< String
363  localtests=.false.
364  if (trim(str1)/=trim(str2)) localtests=.true.
365  if (localtests .or. verbose) then
366  write(stdout,*) '>'//trim(str1)//'<'
367  if (localtests) then
368  write(stdout,*) trim(str1),':',trim(str2), '<-- FAIL'
369  write(stderr,*) trim(str1),':',trim(str2), '<-- FAIL'
370  endif
371  endif
372 end function localtests
373 
374 !> True if i1 is not equal to i2. False otherwise.
375 logical function localtesti(verbose,i1,i2)
376  logical, intent(in) :: verbose !< If true, write results to stdout
377  integer, intent(in) :: i1 !< Integer
378  integer, intent(in) :: i2 !< Integer
379  localtesti=.false.
380  if (i1/=i2) localtesti=.true.
381  if (localtesti .or. verbose) then
382  write(stdout,*) i1,i2
383  if (localtesti) then
384  write(stdout,*) i1,'!=',i2, '<-- FAIL'
385  write(stderr,*) i1,'!=',i2, '<-- FAIL'
386  endif
387  endif
388 end function localtesti
389 
390 !> True if r1 is not equal to r2. False otherwise.
391 logical function localtestr(verbose,r1,r2)
392  logical, intent(in) :: verbose !< If true, write results to stdout
393  real, intent(in) :: r1 !< Float
394  real, intent(in) :: r2 !< Float
395  localtestr=.false.
396  if (r1/=r2) localtestr=.true.
397  if (localtestr .or. verbose) then
398  write(stdout,*) r1,r2
399  if (localtestr) then
400  write(stdout,*) r1,'!=',r2, '<-- FAIL'
401  write(stderr,*) r1,'!=',r2, '<-- FAIL'
402  endif
403  endif
404 end function localtestr
405 
406 !> Returns a directory name that is terminated with a "/" or "./" if the
407 !! argument is an empty string.
408 function slasher(dir)
409  character(len=*), intent(in) :: dir !< A directory to be terminated with a "/"
410  !! or changed to "./" if it is blank.
411  character(len=len(dir)+2) :: slasher
412 
413  if (len_trim(dir) == 0) then
414  slasher = "./"
415  elseif (dir(len_trim(dir):len_trim(dir)) == '/') then
416  slasher = trim(dir)
417  else
418  slasher = trim(dir)//"/"
419  endif
420 end function slasher
421 
422 !> \namespace mom_string_functions
423 !!
424 !! By Alistair Adcroft and Robert Hallberg, last updated Sept. 2013.
425 !!
426 !! The functions here perform a set of useful manipulations of
427 !! character strings. Although they are a part of MOM6, the do not
428 !! require any other MOM software to be useful.
429 
430 end module mom_string_functions
Handy functions for manipulating strings.