diff --git a/doc/specs/stdlib_strings.md b/doc/specs/stdlib_strings.md index 8045d673a..2b29f3d58 100644 --- a/doc/specs/stdlib_strings.md +++ b/doc/specs/stdlib_strings.md @@ -108,3 +108,87 @@ program demo print'(a)', chomp("hello", substring="lo") ! "hel" end program demo ``` + + + +### `starts_with` + +#### Description + +Check if a *string* starts with a given *substring*. + +#### Syntax + +`string = [[stdlib_strings(module):starts_with(interface)]] (string, substring)` + +#### Status + +Experimental + +#### Class + +Pure function. + +#### Argument + +- `string`: Character scalar or [[stdlib_string_type(module):string_type(type)]]. + This argument is intent(in). +- `substring`: Character scalar or [[stdlib_string_type(module):string_type(type)]]. + This argument is intent(in). + +#### Result value + +The result is of scalar logical type. + +#### Example + +```fortran +program demo + use stdlib_strings, only : starts_with + implicit none + print'(a)', starts_with("pattern", "pat") ! T + print'(a)', starts_with("pattern", "ern") ! F +end program demo +``` + + + +### `ends_with` + +#### Description + +Check if a *string* ends with a given *substring*. + +#### Syntax + +`string = [[stdlib_strings(module):ends_with(interface)]] (string, substring)` + +#### Status + +Experimental + +#### Class + +Pure function. + +#### Argument + +- `string`: Character scalar or [[stdlib_string_type(module):string_type(type)]]. + This argument is intent(in). +- `substring`: Character scalar or [[stdlib_string_type(module):string_type(type)]]. + This argument is intent(in). + +#### Result value + +The result is of scalar logical type. + +#### Example + +```fortran +program demo + use stdlib_strings, only : ends_with + implicit none + print'(a)', ends_with("pattern", "ern") ! T + print'(a)', ends_with("pattern", "pat") ! F +end program demo +``` diff --git a/src/stdlib_strings.f90 b/src/stdlib_strings.f90 index 8b0468dd8..0bc83f9ce 100644 --- a/src/stdlib_strings.f90 +++ b/src/stdlib_strings.f90 @@ -10,6 +10,7 @@ module stdlib_strings private public :: strip, chomp + public :: starts_with, ends_with !> Remove leading and trailing whitespace characters. @@ -36,6 +37,28 @@ module stdlib_strings end interface chomp + !> Check whether a string starts with substring or not + !> + !> Version: experimental + interface starts_with + module procedure :: starts_with_string_string + module procedure :: starts_with_string_char + module procedure :: starts_with_char_string + module procedure :: starts_with_char_char + end interface starts_with + + + !> Check whether a string ends with substring or not + !> + !> Version: experimental + interface ends_with + module procedure :: ends_with_string_string + module procedure :: ends_with_string_char + module procedure :: ends_with_char_string + module procedure :: ends_with_char_char + end interface ends_with + + contains @@ -173,4 +196,99 @@ pure function set_to_string(set) result(string) end function set_to_string + !> Check whether a string starts with substring or not + pure function starts_with_char_char(string, substring) result(match) + character(len=*), intent(in) :: string + character(len=*), intent(in) :: substring + logical :: match + integer :: nsub + + nsub = len(substring) + if (len(string) < nsub) then + match = .false. + return + end if + match = string(1:nsub) == substring + + end function starts_with_char_char + + !> Check whether a string starts with substring or not + elemental function starts_with_string_char(string, substring) result(match) + type(string_type), intent(in) :: string + character(len=*), intent(in) :: substring + logical :: match + + match = starts_with(char(string), substring) + + end function starts_with_string_char + + !> Check whether a string starts with substring or not + elemental function starts_with_char_string(string, substring) result(match) + character(len=*), intent(in) :: string + type(string_type), intent(in) :: substring + logical :: match + + match = starts_with(string, char(substring)) + + end function starts_with_char_string + + !> Check whether a string starts with substring or not + elemental function starts_with_string_string(string, substring) result(match) + type(string_type), intent(in) :: string + type(string_type), intent(in) :: substring + logical :: match + + match = starts_with(char(string), char(substring)) + + end function starts_with_string_string + + + !> Check whether a string ends with substring or not + pure function ends_with_char_char(string, substring) result(match) + character(len=*), intent(in) :: string + character(len=*), intent(in) :: substring + logical :: match + integer :: last, nsub + + last = len(string) + nsub = len(substring) + if (last < nsub) then + match = .false. + return + end if + match = string(last-nsub+1:last) == substring + + end function ends_with_char_char + + !> Check whether a string ends with substring or not + elemental function ends_with_string_char(string, substring) result(match) + type(string_type), intent(in) :: string + character(len=*), intent(in) :: substring + logical :: match + + match = ends_with(char(string), substring) + + end function ends_with_string_char + + !> Check whether a string ends with substring or not + elemental function ends_with_char_string(string, substring) result(match) + character(len=*), intent(in) :: string + type(string_type), intent(in) :: substring + logical :: match + + match = ends_with(string, char(substring)) + + end function ends_with_char_string + + !> Check whether a string ends with substring or not + elemental function ends_with_string_string(string, substring) result(match) + type(string_type), intent(in) :: string + type(string_type), intent(in) :: substring + logical :: match + + match = ends_with(char(string), char(substring)) + + end function ends_with_string_string + + end module stdlib_strings diff --git a/src/tests/string/CMakeLists.txt b/src/tests/string/CMakeLists.txt index b875c2d7a..e0d1d9710 100644 --- a/src/tests/string/CMakeLists.txt +++ b/src/tests/string/CMakeLists.txt @@ -1,6 +1,7 @@ ADDTEST(string_assignment) ADDTEST(string_operator) ADDTEST(string_intrinsic) +ADDTEST(string_match) ADDTEST(string_derivedtype_io) ADDTEST(string_functions) ADDTEST(string_strip_chomp) diff --git a/src/tests/string/Makefile.manual b/src/tests/string/Makefile.manual index 1dcf91d78..2e91044a1 100644 --- a/src/tests/string/Makefile.manual +++ b/src/tests/string/Makefile.manual @@ -2,6 +2,7 @@ PROGS_SRC = test_string_assignment.f90 \ test_string_derivedtype_io.f90 \ test_string_functions.f90 \ test_string_intrinsic.f90 \ + test_string_match.f90 \ test_string_operator.f90 \ test_string_strip_chomp.f90 diff --git a/src/tests/string/test_string_match.f90 b/src/tests/string/test_string_match.f90 new file mode 100644 index 000000000..dd2c4b8c2 --- /dev/null +++ b/src/tests/string/test_string_match.f90 @@ -0,0 +1,72 @@ +! SPDX-Identifier: MIT +module test_match + use stdlib_ascii, only : reverse + use stdlib_error, only : check + use stdlib_strings, only : starts_with, ends_with + use stdlib_string_type, only : string_type + implicit none + +contains + + subroutine check_starts_with(string, substring) + character(len=*), intent(in) :: string + character(len=*), intent(in) :: substring + logical :: match + character(len=:), allocatable :: message + + match = index(string, substring) == 1 + if (match) then + message = "Failed to recognize that '"//string//"' starts with '"//substring//"'" + else + message = "Incorrectly found that '"//string//"' starts with '"//substring//"'" + end if + + call check(starts_with(string, substring) .eqv. match, message) + call check(starts_with(string_type(string), substring) .eqv. match, message) + call check(starts_with(string, string_type(substring)) .eqv. match, message) + call check(starts_with(string_type(string), string_type(substring)) .eqv. match, message) + end subroutine check_starts_with + + subroutine test_starts_with + call check_starts_with("pattern", "pat") + call check_starts_with("pat", "pattern") + call check_starts_with("pattern", "ern") + call check_starts_with("ern", "pattern") + end subroutine test_starts_with + + subroutine check_ends_with(string, substring) + character(len=*), intent(in) :: string + character(len=*), intent(in) :: substring + logical :: match + character(len=:), allocatable :: message + + match = index(reverse(string), reverse(substring)) == 1 + if (match) then + message = "Failed to recognize that '"//string//"' ends with '"//substring//"'" + else + message = "Incorrectly found that '"//string//"' ends with '"//substring//"'" + end if + + call check(ends_with(string, substring) .eqv. match, message) + call check(ends_with(string_type(string), substring) .eqv. match, message) + call check(ends_with(string, string_type(substring)) .eqv. match, message) + call check(ends_with(string_type(string), string_type(substring)) .eqv. match, message) + end subroutine check_ends_with + + subroutine test_ends_with + call check_ends_with("pattern", "pat") + call check_ends_with("pat", "pattern") + call check_ends_with("pattern", "ern") + call check_ends_with("ern", "pattern") + end subroutine test_ends_with + +end module test_match + +program tester + use test_match + implicit none + + call test_starts_with + call test_ends_with + +end program tester