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

strings: join, to_c_char #936

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
72 changes: 72 additions & 0 deletions doc/specs/stdlib_strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,43 @@ The result is of the same type as `string`.
{!example/strings/example_zfill.f90!}
```

<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
### `join`

#### Description

Joins an array of strings into a single string. This function concatenates the strings from the input array,
inserting a separator between each string (default: space). A user-defined separator may be provided, The resulting string is returned.


#### Syntax

`cmd = ` [[stdlib_strings(module):join(interface)]] ` (strings, separator)`
perazz marked this conversation as resolved.
Show resolved Hide resolved

#### Status

Experimental

#### Class

Pure function

#### Argument

- `strings`: Array of strings (either `type(string_type)` or `character(len=*)`).
This argument is `intent(in)`. It is an array of strings that will be concatenated together.
- `separator`: `character(len=*)` scalar (optional).
This argument is `intent(in)`. It specifies the separator to be used between the strings. If not provided, the default separator (a space) is used.

#### Result value

The result is of the same type as the elements of `strings` (`type(string_type)` or `character(len=:), allocatable`).

#### Example

```fortran
{!example/strings/example_join.f90!}
```

<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
### `to_string`
Expand Down Expand Up @@ -498,3 +535,38 @@ The result is an `allocatable` length `character` scalar with up to `128` cached
```fortran
{!example/strings/example_to_string.f90!}
```

<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
### `to_c_string`
perazz marked this conversation as resolved.
Show resolved Hide resolved

#### Description

Convert a Fortran `character` string or a `type(string_type)` variable to a C character array.
This function converts a Fortran string into a C-style array of characters, ensuring proper null-termination for use in C functions or libraries.

#### Syntax

`cstr = ` [[stdlib_strings(module):to_c_string(function)]] ` (value)`

#### Status

Experimental

#### Class

Pure function.

#### Argument

- `value`: Shall be a `character(len=*)` string or a `type(string_type)` variable. It is an `intent(in)` argument.
The Fortran string that will be converted to a C character array.
perazz marked this conversation as resolved.
Show resolved Hide resolved

#### Result value

The result is a `character(kind=c_char)` array with a dimension of `len(value) + 1` to accommodate the null terminator.

#### Example

```fortran
{!example/strings/example_to_c_string.f90!}
```
4 changes: 3 additions & 1 deletion example/strings/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ ADD_EXAMPLE(chomp)
ADD_EXAMPLE(count)
ADD_EXAMPLE(ends_with)
ADD_EXAMPLE(find)
ADD_EXAMPLE(join)
ADD_EXAMPLE(padl)
ADD_EXAMPLE(padr)
ADD_EXAMPLE(replace_all)
ADD_EXAMPLE(slice)
ADD_EXAMPLE(starts_with)
ADD_EXAMPLE(strip)
ADD_EXAMPLE(to_string)
ADD_EXAMPLE(to_c_string)
ADD_EXAMPLE(zfill)
ADD_EXAMPLE(string_to_number)
ADD_EXAMPLE(stream_of_strings_to_numbers)
ADD_EXAMPLE(stream_of_strings_to_numbers)
21 changes: 21 additions & 0 deletions example/strings/example_join.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
program example_join
use stdlib_strings, only: join
implicit none

character(len=:), allocatable :: line
character(*), parameter :: words(3) = [character(7) :: "Hello", "World", "Fortran"]

! Default separator (space)
line = join(words)
print *, "'" // line // "'" !! 'Hello World Fortran'

! Custom separator
line = join(words, "_")
print *, "'" // line // "'" !! 'Hello_World_Fortran'

! Custom 2-character separator
line = join(words, ", ")
print *, "'" // line // "'" !! 'Hello, World, Fortran'

stop 0
perazz marked this conversation as resolved.
Show resolved Hide resolved
end program example_join
22 changes: 22 additions & 0 deletions example/strings/example_to_c_string.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
program example_to_c_string
use stdlib_strings, only: to_c_string
use stdlib_string_type, only: string_type
use stdlib_kinds, only: c_char
implicit none

character(kind=c_char), allocatable :: cstr(:),cstr2(:)
character(*), parameter :: hello = "Hello, World!"

! Convert character array
cstr = to_c_string(hello)

! Convert string type
cstr2 = to_c_string(string_type(hello))

if (size(cstr)==size(cstr2) .and. all(cstr==cstr2)) then
stop 0
perazz marked this conversation as resolved.
Show resolved Hide resolved
else
error stop 'String conversion error'
end if

end program example_to_c_string
4 changes: 2 additions & 2 deletions src/stdlib_kinds.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
!> The specification of this module is available [here](../page/specs/stdlib_kinds.html).
module stdlib_kinds
use iso_fortran_env, only: int8, int16, int32, int64
use iso_c_binding, only: c_bool
use iso_c_binding, only: c_bool, c_char
implicit none
private
public :: sp, dp, xdp, qp, int8, int16, int32, int64, lk, c_bool
public :: sp, dp, xdp, qp, int8, int16, int32, int64, lk, c_bool, c_char

!> Single precision real numbers
integer, parameter :: sp = selected_real_kind(6)
Expand Down
121 changes: 118 additions & 3 deletions src/stdlib_strings.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
!> 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, repeat, len
use stdlib_string_type, only: string_type, char, verify, repeat, len, len_trim, move
use stdlib_optval, only: optval
use stdlib_kinds, only: sp, dp, xdp, qp, int8, int16, int32, int64, lk, c_bool
use stdlib_kinds, only: sp, dp, xdp, qp, int8, int16, int32, int64, lk, c_bool, c_char
use iso_c_binding, only: c_null_char
implicit none
private

public :: to_string
public :: to_c_string
public :: strip, chomp
public :: starts_with, ends_with
public :: slice, find, replace_all, padl, padr, count, zfill
public :: slice, find, replace_all, padl, padr, count, zfill, join

!> Version: experimental
!>
Expand Down Expand Up @@ -43,6 +45,15 @@ module stdlib_strings
#:endfor
end interface to_string

!> Version: experimental
!>
!> Format or transfer other types as a string.
!> ([Specification](../page/specs/stdlib_strings.html#to_c_string))
interface to_c_string
module procedure to_c_string_from_char
module procedure to_c_string_from_string
end interface to_c_string

!> Remove leading and trailing whitespace characters.
!>
!> Version: experimental
Expand Down Expand Up @@ -164,6 +175,17 @@ module stdlib_strings
module procedure :: zfill_char
end interface zfill

!> Version: experimental
!>
!> Joins an array of strings into a single string.
!> The chunks are separated with a space, or an optional user-defined separator.
!> [Specifications](../page/specs/stdlib_strings.html#join)
interface join
module procedure :: join_string
module procedure :: join_char
end interface join


contains


Expand Down Expand Up @@ -943,5 +965,98 @@ contains

end function zfill_char

!> Convert a Fortran character string to a C character array
!>
!> Version: experimental
pure function to_c_string_from_char(value) result(cstr)
character(len=*), intent(in) :: value
character(kind=c_char) :: cstr(len(value)+1)
integer :: i,lv
lv = len(value)
do concurrent (i=1:lv)
cstr(i) = value(i:i)
end do
cstr(lv+1) = c_null_char
end function to_c_string_from_char

!> Convert a Fortran string type to a C character array
!>
!> Version: experimental
pure function to_c_string_from_string(value) result(cstr)
type(string_type), intent(in) :: value
character(kind=c_char) :: cstr(len(value)+1)
integer :: i,lv
lv = len(value)
do concurrent (i=1:lv)
cstr(i) = char(value,pos=i)
end do
cstr(lv+1) = c_null_char
end function to_c_string_from_string

!> Joins a list of strings with a separator (default: space).
!> Returns a new string
pure function join_string(strings, separator) result(cmd)
type(string_type), intent(in) :: strings(:)
character(len=*), intent(in), optional :: separator
type(string_type) :: cmd
integer :: ltot, i, lt, pos
character(len=:), allocatable :: sep,cmd_char
! Determine separator: use user-provided separator or default space
if (present(separator)) then
sep = separator
else
sep = ' '
end if
! Calculate the total length required, including separators
ltot = sum(len_trim(strings)) + (size(strings) - 1) * len(sep)
allocate(character(len=ltot) :: cmd_char)

! Concatenate strings with separator
pos = 0
do i = 1, size(strings)
lt = len_trim(strings(i))
cmd_char(pos+1:pos+lt) = char(strings(i),1,lt)
pos = pos + lt
if (i < size(strings)) then
cmd_char(pos+1:pos+len(sep)) = sep
pos = pos + len(sep)
end if
end do

call move(from=cmd_char,to=cmd)

end function join_string

!> Joins a list of strings with a separator (default: space).
!> Returns a new string
pure function join_char(strings, separator) result(cmd)
character(*), intent(in) :: strings(:)
character(len=*), intent(in), optional :: separator
character(len=:), allocatable :: cmd
integer :: ltot, i, lt, pos
character(len=:), allocatable :: sep
! Determine separator: use user-provided separator or default space
if (present(separator)) then
sep = separator
else
sep = ' '
end if
! Calculate the total length required, including separators
ltot = sum(len_trim(strings)) + (size(strings) - 1) * len(sep)
allocate(character(len=ltot) :: cmd)

cmd = repeat(' ',ltot)
! Concatenate strings with separator
pos = 0
do i = 1, size(strings)
lt = len_trim(strings(i))
cmd(pos+1:pos+lt) = strings(i)(1:lt)
pos = pos + lt
if (i < size(strings)) then
cmd(pos+1:pos+len(sep)) = sep
pos = pos + len(sep)
end if
end do
end function join_char

end module stdlib_strings
31 changes: 29 additions & 2 deletions test/string/test_string_match.f90
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
module test_string_match
use testdrive, only : new_unittest, unittest_type, error_type, check
use stdlib_ascii, only : reverse
use stdlib_strings, only : starts_with, ends_with
use stdlib_strings, only : starts_with, ends_with, join
use stdlib_string_type, only : string_type
implicit none

Expand All @@ -16,7 +16,8 @@ subroutine collect_string_match(testsuite)

testsuite = [ &
new_unittest("starts_with", test_starts_with), &
new_unittest("ends_with", test_ends_with) &
new_unittest("ends_with", test_ends_with), &
new_unittest("join", test_join) &
]
end subroutine collect_string_match

Expand Down Expand Up @@ -77,6 +78,32 @@ subroutine check_ends_with(error, string, substring)
call check(error, ends_with(string_type(string), string_type(substring)) .eqv. match, message)
end subroutine check_ends_with

subroutine test_join(error)
type(error_type), allocatable, intent(out) :: error
character(len=5) :: test_strings(3)

test_strings = [character(5) :: "one", "two", "three"]
call check_join(error, test_strings, " ", "one two three")
if (allocated(error)) return
call check_join(error, test_strings, ",", "one,two,three")
if (allocated(error)) return
call check_join(error, test_strings, "-", "one-two-three")
end subroutine test_join

subroutine check_join(error, strings, separator, expected)
type(error_type), allocatable, intent(out) :: error
character(len=*), intent(in) :: strings(:)
character(len=*), intent(in) :: separator
character(len=*), intent(in) :: expected
character(len=:), allocatable :: joined
character(len=:), allocatable :: message

joined = join(strings, separator)
message = "'join' error: Expected '" // expected // "' but got '" // joined // "'"
call check(error, joined == expected, message)

end subroutine check_join

subroutine test_ends_with(error)
type(error_type), allocatable, intent(out) :: error
call check_ends_with(error, "pattern", "pat")
Expand Down
Loading
Loading