Skip to content

Add format_string routine to format other types to strings #444

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

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e3829b4
add:
zoziha Jun 25, 2021
da2881c
remove a redundant comma in doc
St-Maxwell Jun 26, 2021
c622a5b
fix format_string routines update its doc.
zoziha Jun 26, 2021
193f07f
Expand unit testing for more cases
awvwgk Jun 26, 2021
7f6c3c6
Merge pull request #1 from awvwgk/format_string
St-Maxwell Jun 26, 2021
41c5783
Try to make default formatter tests compiler independent
awvwgk Jun 26, 2021
785902c
fix test_strings_format_string.f90 for [this problem](https://github.…
zoziha Jun 26, 2021
39d5d13
Merge branch 'zoziha/feature/format_string' of https://github.com/St-…
zoziha Jun 26, 2021
99954bb
update test_string_format_string.f90
zoziha Jun 26, 2021
f9f7755
Make invalid logical example more compiler agnostic
awvwgk Jun 26, 2021
e7ce1e7
update format_string example in stdlib_string.md(doc)
zoziha Jun 27, 2021
65ab05d
improved aesthetics to make code consistent with stdlib's format
aman-godara Jun 27, 2021
34fa3cd
improved aesthetics of test file and documentation
aman-godara Jun 28, 2021
7ae89fb
Merge pull request #2 from Aman-Godara/format_string
zoziha Jun 28, 2021
1fe6a07
renamed strings_format_string to string_format_string
aman-godara Jul 1, 2021
f155525
Merge pull request #3 from Aman-Godara/format_string
St-Maxwell Jul 1, 2021
f1bc676
Merge branch 'master' into zoziha/feature/format_string
zoziha Jul 2, 2021
835de22
Fix manual Makefile build
awvwgk Jul 3, 2021
b05cbae
rename `format_string` to `format_to_string`;
zoziha Jul 5, 2021
e646fc5
Merge branch 'master' into zoziha/feature/format_string
zoziha Jul 5, 2021
c717724
Merge `stdlib_ascii(module):to_string(interface)` to `stdlib_strings(…
zoziha Aug 3, 2021
7fa847a
Merge branch 'master' into merge_tostring
zoziha Aug 3, 2021
bdc33f5
Some clean works for `to_string` func.
zoziha Aug 4, 2021
f3c17a0
Fix `test_string_to_string.f90`: `merge` -> `optval`
zoziha Aug 11, 2021
ce272d7
Merge branch 'master' into zoziha/feature/format_string
zoziha Aug 11, 2021
527daed
Consistent indentation in Makefile
milancurcic Aug 19, 2021
32f9837
Improve `to_string`: add support for `format` like `'f6.2'`.
zoziha Aug 22, 2021
3e31220
Merge branch 'zoziha/feature/format_string' of https://github.com/St-…
zoziha Aug 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions doc/specs/stdlib_strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,63 @@ program demo_find

end program demo_find
```

<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
### `format_string`

#### Description

Format or transfer a integer/real/complex/logical variable as a character sequence.


#### Syntax

`format_string = [[stdlib_strings(module):format_string(interface)]] (value, [, format])`

#### Status

Experimental

#### Class

Pure function

#### Argument

- `value`: Integer/real/complex/logical scalar.
This argument is intent(in).
- `format`: Character scalar like `'(F6.2)'`.
This argument is intent(in) and optional.

#### Result value

The result is a allocatable length Character scalar.

#### Example

```fortran
program demo_strings_format_string
use, non_intrinsic :: stdlib_strings, only: format_string
implicit none
print *, 'format_string(complex) : '
print *, format_string((1, 1)) ! (1.00000000,1.00000000)
print *, format_string((1, 1), '(F6.2)') ! (1.00,1.00)
print *, format_string((1, 1), '(F6.2)'), format_string((2, 2), '(F7.3)') ! (1.00,1.00)(2.000,2.000)
print *, 'format_string(integer) : '
print *, format_string(100) ! 100
print *, format_string(100, '(I6)') ! 100
print *, format_string(100, '(I6)'), format_string(1000, '(I7)') ! 1001000
print *, 'format_string(real) : '
print *, format_string(100.) ! 100.000000
print *, format_string(100., '(F6.2)') ! 100.00
print *, format_string(100., '(F6.2)'), &
format_string(1000., '(F7.3)'), format_string(1000, '(F7.3)') ! 100.00********
!! Wrong demonstration
print *, 'format_string(logical) : '
print *, format_string(.true.) ! T
print *, format_string(.true., '(L2)') ! T
print *, format_string(.false., '(L2)'), format_string(.true., '(L5)'), &
format_string(.false., '(I5)') ! FT*
!! Wrong demonstration
end program demo_strings_format_string
```
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ set(fppFiles
stdlib_stats_distribution_PRNG.fypp
stdlib_math.fypp
stdlib_string_type.fypp
stdlib_strings_format_string.fypp
stdlib_strings.fypp
)


Expand All @@ -47,7 +49,6 @@ set(SRC
stdlib_error.f90
stdlib_kinds.f90
stdlib_logger.f90
stdlib_strings.f90
stdlib_system.F90
${outFiles}
)
Expand Down
9 changes: 6 additions & 3 deletions src/Makefile.manual
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ SRCFYPP =\
stdlib_stats_var.fypp \
stdlib_math.fypp \
stdlib_stats_distribution_PRNG.fypp \
stdlib_string_type.fypp
stdlib_string_type.fypp \
stdlib_strings.fypp \
stdlib_strings_format_string.fypp

SRC = f18estop.f90 \
stdlib_error.f90 \
stdlib_kinds.f90 \
stdlib_logger.f90 \
stdlib_strings.f90 \
$(SRCGEN)

LIB = libstdlib.a
Expand Down Expand Up @@ -129,5 +130,7 @@ stdlib_string_type.o: stdlib_ascii.o \
stdlib_kinds.o
stdlib_strings.o: stdlib_ascii.o \
stdlib_string_type.o \
stdlib_optval.o
stdlib_optval.o \
stdlib_kinds.o
stdlib_math.o: stdlib_kinds.o
stdlib_strings_format_string.o: stdlib_strings.o
19 changes: 18 additions & 1 deletion src/stdlib_strings.f90 → src/stdlib_strings.fypp
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
! SPDX-Identifier: MIT

#:include "common.fypp"
#:set KINDS_TYPES = REAL_KINDS_TYPES + INT_KINDS_TYPES + LOG_KINDS_TYPES &
& + CMPLX_KINDS_TYPES
!> This module implements basic string handling routines.
!>
!> The specification of this module is available [here](../page/specs/stdlib_strings.html).
module stdlib_strings
use stdlib_ascii, only: whitespace
use stdlib_string_type, only: string_type, char, verify
use stdlib_optval, only: optval
use stdlib_kinds, only: sp, dp, qp, int8, int16, int32, int64, lk, c_bool
implicit none
private

public :: format_string
public :: strip, chomp
public :: starts_with, ends_with
public :: slice, find

interface format_string
!! version: experimental
!!
!! Format ${type}$ variable as character sequence
!! ([Specification](../page/specs/stdlib_string.html#description))
#:for kind, type in KINDS_TYPES
pure module function format_string_${type[0]}$${kind}$(val, fmt) result(string)
character(len=:), allocatable :: string
${type}$, intent(in) :: val
character(len=*), intent(in), optional :: fmt
end function format_string_${type[0]}$${kind}$
#:endfor
end interface format_string

!> Remove leading and trailing whitespace characters.
!>
Expand Down
52 changes: 52 additions & 0 deletions src/stdlib_strings_format_string.fypp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#:include "common.fypp"
#:set RIL_KINDS_TYPES = REAL_KINDS_TYPES + INT_KINDS_TYPES + LOG_KINDS_TYPES
submodule (stdlib_strings) stdlib_strings_format_string

implicit none
integer, parameter :: buffer_len = 512

contains

#:for kind, type in RIL_KINDS_TYPES
module procedure format_string_${type[0]}$${kind}$
!! Format ${type}$ variable as character sequence
character(len=buffer_len) :: buffer
integer :: stat
if(present(fmt)) then
write(buffer, fmt, iostat=stat) val
if(stat == 0) then
string = trim(adjustl(buffer))
else
string = '*'
!!\TODO: *?
Copy link
Member

Choose a reason for hiding this comment

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

For a meaningful number of asterisks in the output we must be able to parse format strings and determine the width of the field we are writing into. Just writing a single asterisk until we are able to do so in stdlib sounds like a good compromise for now. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is OK, just writing a single asterisk until we are able to do so in stdlib.😜

Copy link
Contributor

Choose a reason for hiding this comment

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

image
I think the * sign here should be distinguished a little bit more, for example, written as [*], !*! and the like can be distinguished from the ***** sign (too narrow formatter for variables) before and after it.

Copy link
Member

Choose a reason for hiding this comment

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

Checking a failed formatter should be simple, currently I'm using starts_with(format_string(var, "(...)"), "*"). This is not fail-safe as it would match a formatter like '("*", g0)' as well. Using format_string should imply that we trade some of the versatility of the internal IO approach for having a function.

How about returning an empty string on failure? This would be clearly distinct from a too narrow formatter?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is mainly to remind the users: an empty string may not be effectively fed back to the user, this is a format_string failure.

  1. We need to notify the user more clearly that this has failed;
  2. The string output after failure is simple enough;
  3. Does not hinder the effective operation of the main process of our Fortran program.

end if
else
write(buffer, *, iostat=stat) val
if(stat == 0) then
string = trim(adjustl(buffer))
else
string = '*'
!!\TODO: *?
end if
end if
end procedure format_string_${type[0]}$${kind}$
#:endfor

#:for kind, type in CMPLX_KINDS_TYPES
module procedure format_string_${type[0]}$${kind}$
!! Format ${type}$ variable as character sequence
character(len=buffer_len) :: buffer
if(present(fmt)) then
write(buffer, *) '('//&
format_string_r${kind}$(real(val), fmt)//','// &
format_string_r${kind}$(aimag(val), fmt)//')'
else
write(buffer, *) '('//&
format_string_r${kind}$(real(val))//','// &
format_string_r${kind}$(aimag(val))//')'
end if
string = trim(adjustl(buffer))
end procedure format_string_${type[0]}$${kind}$
#:endfor

end submodule stdlib_strings_format_string
1 change: 1 addition & 0 deletions src/tests/string/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ ADDTEST(string_match)
ADDTEST(string_derivedtype_io)
ADDTEST(string_functions)
ADDTEST(string_strip_chomp)
ADDTEST(strings_format_string)
3 changes: 2 additions & 1 deletion src/tests/string/Makefile.manual
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ PROGS_SRC = test_string_assignment.f90 \
test_string_intrinsic.f90 \
test_string_match.f90 \
test_string_operator.f90 \
test_string_strip_chomp.f90
test_string_strip_chomp.f90 \
test_strings_format_string.f90


include ../Makefile.manual.test.mk
24 changes: 24 additions & 0 deletions src/tests/string/test_strings_format_string.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
program test_strings_format_string
use, non_intrinsic :: stdlib_strings, only: format_string
implicit none
print *, 'format_string(complex) : '
print *, format_string((1, 1))
print *, format_string((1, 1), '(F6.2)')
print *, format_string((1, 1), '(F6.2)'), format_string((2, 2), '(F7.3)')
print *, 'format_string(integer) : '
print *, format_string(100)
print *, format_string(100, '(I6)')
print *, format_string(100, '(I6)'), format_string(1000, '(I7)')
print *, 'format_string(real) : '
print *, format_string(100.)
print *, format_string(100., '(F6.2)')
print *, format_string(100., '(F6.2)'), &
format_string(1000., '(F7.3)'), format_string(1000, '(F7.3)')
!! Wrong demonstration
print *, 'format_string(logical) : '
print *, format_string(.true.)
print *, format_string(.true., '(L2)')
print *, format_string(.false., '(L2)'), format_string(.true., '(L5)'), &
format_string(.false., '(I5)')
!! Wrong demonstration
end program test_strings_format_string