Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend string interface in fms_string_utils_mod #1142

Merged
merged 16 commits into from
Mar 9, 2023
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ foreach(kind ${kinds})
target_include_directories(${libTgt}_f PRIVATE include
fms
fms2_io/include
string_utils/include
mpp/include
diag_manager/include
constants4
Expand Down Expand Up @@ -334,6 +335,7 @@ foreach(kind ${kinds})
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/fms>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/fms2_io/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/string_utils/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/mpp/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/diag_manager/include>)

Expand Down
5 changes: 4 additions & 1 deletion string_utils/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# package.

# Include .h and .mod files.
AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/string_utils/include
AM_FCFLAGS = $(FC_MODINC). $(FC_MODOUT)$(MODDIR)

# Build this uninstalled convenience library.
Expand All @@ -30,6 +30,9 @@ noinst_LTLIBRARIES = libstring_utils.la
# The convenience library depends on its source.
libstring_utils_la_SOURCES = \
fms_string_utils.F90 \
include/fms_string_utils.inc \
include/fms_string_utils_r4.fh \
include/fms_string_utils_r8.fh \
fms_string_utils_binding.c

MODFILES = \
Expand Down
84 changes: 55 additions & 29 deletions string_utils/fms_string_utils.F90
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
!> @{
module fms_string_utils_mod
use, intrinsic :: iso_c_binding
use platform_mod, only: r4_kind, r8_kind
use mpp_mod

implicit none
Expand All @@ -43,6 +44,7 @@ module fms_string_utils_mod
public :: fms_cstring2cpointer
public :: string
public :: string_copy
public :: stringify
!> @}

interface
Expand Down Expand Up @@ -112,11 +114,12 @@ subroutine c_free(ptr) bind(c,name="free")
module procedure cpointer_fortran_conversion
end interface

!> Converts a number to a string
!> Converts an array of real numbers to a string
!> @ingroup fms_mod
interface string
module procedure string_from_integer
module procedure string_from_real
interface stringify
module procedure stringify_1d_r4, stringify_1d_r8
module procedure stringify_2d_r4, stringify_2d_r8
module procedure stringify_3d_r4, stringify_3d_r8
end interface

!> @addtogroup fms_string_utils_mod
Expand Down Expand Up @@ -237,31 +240,51 @@ subroutine fms_f2c_string (dest, str_in)
enddo
end subroutine fms_f2c_string


!> @brief Converts an integer to a string
!> @return The integer as a string
function string_from_integer(i) result (res)
integer, intent(in) :: i !< Integer to be converted to a string
character(:),allocatable :: res !< String converted frominteger
character(range(i)+2) :: tmp !< Temp string that is set to correct size
write(tmp,'(i0)') i
res = trim(tmp)
return

end function string_from_integer

!#######################################################################
!> @brief Converts a real to a string
!> @return The real number as a string
function string_from_real(r)
real, intent(in) :: r !< Real number to be converted to a string
character(len=32) :: string_from_real

write(string_from_real,*) r

return

end function string_from_real
!> @brief Converts a number or a Boolean value to a string
!> @return The argument as a string
function string(v, fmt)
class(*), intent(in) :: v ! Value to be converted to a string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is not correctly doxygenized

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16356ac.

character(*), intent(in), optional :: fmt !< Optional format string for a real argument
character(:), allocatable :: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's always being allocated to a length of 32, why not just make this a len=32 string instead of allocatable? I'm not saying you have to do this, I'm just wondering if you think that's a better option. That would also make this closer to being a pure/elemental function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By making it allocatable, leading and trailing whitespace can be stripped so that the user doesn't need to do trim(string(x)). For me at least, this outweighs the benefit of making the function elemental (although I'm biased by the fact that I've only used string() for generating unit test log messages).


select type(v)
type is (logical)
if (v) then
string = "True"
else
string = "False"
endif

type is (integer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add integer(i4_kind) and integer(i8_kind)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16356ac.

allocate(character(32) :: string)
write(string, '(i0)') v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user provides a format statement for an integer or a logical or a string, it will be unused. This could produce unexpected behavior for the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16356ac via a warning message (in the case of a logical value) and by using fmt (in the case of an integral value).

string = trim(adjustl(string))

type is (real(r4_kind))
allocate(character(32) :: string)
if (present(fmt)) then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk if you can check this, but how will you know that the user's format will work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think fmt validation might not be worth the effort, since Fortran will just throw a runtime error if the format string is invalid. The most catastrophic scenario I can imagine would be a format string wider than 32 characters, but the runtime library seems to check this and terminate the program if this happens. It might be a good idea to add a unit test for this case.

write(string, "(" // fmt // ")") v
else
write(string, *) v
endif
string = trim(adjustl(string))

type is (real(r8_kind))
allocate(character(32) :: string)
if (present(fmt)) then
write(string, "(" // fmt // ")") v
else
write(string, *) v
endif
string = trim(adjustl(string))

type is (character(*))
string = v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should avoid this. The class(*) string handling is inconsistent across compilers and this will only lead to problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 16356ac.


class default
call mpp_error(FATAL, "string() called with incompatible argument")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this check was not writing to stdout, then this might be able to be an elemental function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you keep the error message, it should be more comprehensive. "string() called with incompatible argument. Must be of type integer(kind=4), integer(kind=8), real(kind=4), real(kind=8), or logical."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16356ac.

end select
end function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please put the name of the function here

end function string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16356ac.

!> @brief Safely copy a string from one buffer to another.
subroutine string_copy(dest, source, check_for_null)
Expand Down Expand Up @@ -290,6 +313,9 @@ subroutine string_copy(dest, source, check_for_null)
dest = adjustl(trim(source(1:i)))
end subroutine string_copy

#include "fms_string_utils_r4.fh"
#include "fms_string_utils_r8.fh"

end module fms_string_utils_mod
!> @}
! close documentation grouping
87 changes: 87 additions & 0 deletions string_utils/include/fms_string_utils.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
!***********************************************************************
!* GNU Lesser General Public License
!*
!* This file is part of the GFDL Flexible Modeling System (FMS).
!*
!* FMS is free software: you can redistribute it and/or modify it under
!* the terms of the GNU Lesser General Public License as published by
!* the Free Software Foundation, either version 3 of the License, or (at
!* your option) any later version.
!*
!* FMS is distributed in the hope that it will be useful, but WITHOUT
!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
!* for more details.
!*
!* You should have received a copy of the GNU Lesser General Public
!* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
!***********************************************************************

!> @brief Converts a 1D array of real numbers to a string
!> @return The 1D array as a string
function STRINGIFY_1D_(arr, fmt)
real(STRING_UTILS_KIND_), dimension(:), intent(in) :: arr !< Real array to be converted to a string
character(*), intent(in), optional :: fmt !< Optional format string for the real array entries
character(:), allocatable :: STRINGIFY_1D_
integer :: i, n

n = size(arr)

if (n .gt. 0) then
STRINGIFY_1D_ = "[" // string(arr(1), fmt)
else
STRINGIFY_1D_ = "["
endif

do i = 2,n
STRINGIFY_1D_ = STRINGIFY_1D_ // ", " // string(arr(i), fmt)
enddo

STRINGIFY_1D_ = STRINGIFY_1D_ // "]"
end function

!> @brief Converts a 2D array of real numbers to a string
!> @return The 2D array as a string
function STRINGIFY_2D_(arr, fmt)
real(STRING_UTILS_KIND_), dimension(:,:), intent(in) :: arr !< Real array to be converted to a string
character(*), intent(in), optional :: fmt !< Optional format string for the real array entries
character(:), allocatable :: STRINGIFY_2D_
integer :: i, n

n = size(arr, 2)

if (n .gt. 0) then
STRINGIFY_2D_ = "[" // STRINGIFY_1D_(arr(:,1), fmt)
else
STRINGIFY_2D_ = "["
endif

do i = 2,n
STRINGIFY_2D_ = STRINGIFY_2D_ // ", " // STRINGIFY_1D_(arr(:,i), fmt)
enddo

STRINGIFY_2D_ = STRINGIFY_2D_ // "]"
end function

!> @brief Converts a 3D array of real numbers to a string
!> @return The 3D array as a string
function STRINGIFY_3D_(arr, fmt)
real(STRING_UTILS_KIND_), dimension(:,:,:), intent(in) :: arr !< Real array to be converted to a string
character(*), intent(in), optional :: fmt !< Optional format string for the real array entries
character(:), allocatable :: STRINGIFY_3D_
integer :: i, n

n = size(arr, 3)

if (n .gt. 0) then
STRINGIFY_3D_ = "[" // STRINGIFY_2D_(arr(:,:,1), fmt)
else
STRINGIFY_3D_ = "["
endif

do i = 2,n
STRINGIFY_3D_ = STRINGIFY_3D_ // ", " // STRINGIFY_2D_(arr(:,:,i), fmt)
enddo

STRINGIFY_3D_ = STRINGIFY_3D_ // "]"
end function
30 changes: 30 additions & 0 deletions string_utils/include/fms_string_utils_r4.fh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
!***********************************************************************
!* GNU Lesser General Public License
!*
!* This file is part of the GFDL Flexible Modeling System (FMS).
!*
!* FMS is free software: you can redistribute it and/or modify it under
!* the terms of the GNU Lesser General Public License as published by
!* the Free Software Foundation, either version 3 of the License, or (at
!* your option) any later version.
!*
!* FMS is distributed in the hope that it will be useful, but WITHOUT
!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
!* for more details.
!*
!* You should have received a copy of the GNU Lesser General Public
!* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
!***********************************************************************

#define STRING_UTILS_KIND_ r4_kind
#define STRINGIFY_1D_ stringify_1d_r4
#define STRINGIFY_2D_ stringify_2d_r4
#define STRINGIFY_3D_ stringify_3d_r4

#include "fms_string_utils.inc"

#undef STRING_UTILS_KIND_
#undef STRINGIFY_1D_
#undef STRINGIFY_2D_
#undef STRINGIFY_3D_
30 changes: 30 additions & 0 deletions string_utils/include/fms_string_utils_r8.fh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
!***********************************************************************
!* GNU Lesser General Public License
!*
!* This file is part of the GFDL Flexible Modeling System (FMS).
!*
!* FMS is free software: you can redistribute it and/or modify it under
!* the terms of the GNU Lesser General Public License as published by
!* the Free Software Foundation, either version 3 of the License, or (at
!* your option) any later version.
!*
!* FMS is distributed in the hope that it will be useful, but WITHOUT
!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
!* for more details.
!*
!* You should have received a copy of the GNU Lesser General Public
!* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
!***********************************************************************

#define STRING_UTILS_KIND_ r8_kind
#define STRINGIFY_1D_ stringify_1d_r8
#define STRINGIFY_2D_ stringify_2d_r8
#define STRINGIFY_3D_ stringify_3d_r8

#include "fms_string_utils.inc"

#undef STRING_UTILS_KIND_
#undef STRINGIFY_1D_
#undef STRINGIFY_2D_
#undef STRINGIFY_3D_
Loading