Skip to content

Commit 1cbb200

Browse files
authored
Implement starts_with and ends_with functions (#384)
1 parent 3621b03 commit 1cbb200

File tree

5 files changed

+276
-0
lines changed

5 files changed

+276
-0
lines changed

doc/specs/stdlib_strings.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,87 @@ program demo
108108
print'(a)', chomp("hello", substring="lo") ! "hel"
109109
end program demo
110110
```
111+
112+
113+
<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
114+
### `starts_with`
115+
116+
#### Description
117+
118+
Check if a *string* starts with a given *substring*.
119+
120+
#### Syntax
121+
122+
`string = [[stdlib_strings(module):starts_with(interface)]] (string, substring)`
123+
124+
#### Status
125+
126+
Experimental
127+
128+
#### Class
129+
130+
Pure function.
131+
132+
#### Argument
133+
134+
- `string`: Character scalar or [[stdlib_string_type(module):string_type(type)]].
135+
This argument is intent(in).
136+
- `substring`: Character scalar or [[stdlib_string_type(module):string_type(type)]].
137+
This argument is intent(in).
138+
139+
#### Result value
140+
141+
The result is of scalar logical type.
142+
143+
#### Example
144+
145+
```fortran
146+
program demo
147+
use stdlib_strings, only : starts_with
148+
implicit none
149+
print'(a)', starts_with("pattern", "pat") ! T
150+
print'(a)', starts_with("pattern", "ern") ! F
151+
end program demo
152+
```
153+
154+
155+
<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
156+
### `ends_with`
157+
158+
#### Description
159+
160+
Check if a *string* ends with a given *substring*.
161+
162+
#### Syntax
163+
164+
`string = [[stdlib_strings(module):ends_with(interface)]] (string, substring)`
165+
166+
#### Status
167+
168+
Experimental
169+
170+
#### Class
171+
172+
Pure function.
173+
174+
#### Argument
175+
176+
- `string`: Character scalar or [[stdlib_string_type(module):string_type(type)]].
177+
This argument is intent(in).
178+
- `substring`: Character scalar or [[stdlib_string_type(module):string_type(type)]].
179+
This argument is intent(in).
180+
181+
#### Result value
182+
183+
The result is of scalar logical type.
184+
185+
#### Example
186+
187+
```fortran
188+
program demo
189+
use stdlib_strings, only : ends_with
190+
implicit none
191+
print'(a)', ends_with("pattern", "ern") ! T
192+
print'(a)', ends_with("pattern", "pat") ! F
193+
end program demo
194+
```

src/stdlib_strings.f90

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module stdlib_strings
1010
private
1111

1212
public :: strip, chomp
13+
public :: starts_with, ends_with
1314

1415

1516
!> Remove leading and trailing whitespace characters.
@@ -36,6 +37,28 @@ module stdlib_strings
3637
end interface chomp
3738

3839

40+
!> Check whether a string starts with substring or not
41+
!>
42+
!> Version: experimental
43+
interface starts_with
44+
module procedure :: starts_with_string_string
45+
module procedure :: starts_with_string_char
46+
module procedure :: starts_with_char_string
47+
module procedure :: starts_with_char_char
48+
end interface starts_with
49+
50+
51+
!> Check whether a string ends with substring or not
52+
!>
53+
!> Version: experimental
54+
interface ends_with
55+
module procedure :: ends_with_string_string
56+
module procedure :: ends_with_string_char
57+
module procedure :: ends_with_char_string
58+
module procedure :: ends_with_char_char
59+
end interface ends_with
60+
61+
3962
contains
4063

4164

@@ -173,4 +196,99 @@ pure function set_to_string(set) result(string)
173196
end function set_to_string
174197

175198

199+
!> Check whether a string starts with substring or not
200+
pure function starts_with_char_char(string, substring) result(match)
201+
character(len=*), intent(in) :: string
202+
character(len=*), intent(in) :: substring
203+
logical :: match
204+
integer :: nsub
205+
206+
nsub = len(substring)
207+
if (len(string) < nsub) then
208+
match = .false.
209+
return
210+
end if
211+
match = string(1:nsub) == substring
212+
213+
end function starts_with_char_char
214+
215+
!> Check whether a string starts with substring or not
216+
elemental function starts_with_string_char(string, substring) result(match)
217+
type(string_type), intent(in) :: string
218+
character(len=*), intent(in) :: substring
219+
logical :: match
220+
221+
match = starts_with(char(string), substring)
222+
223+
end function starts_with_string_char
224+
225+
!> Check whether a string starts with substring or not
226+
elemental function starts_with_char_string(string, substring) result(match)
227+
character(len=*), intent(in) :: string
228+
type(string_type), intent(in) :: substring
229+
logical :: match
230+
231+
match = starts_with(string, char(substring))
232+
233+
end function starts_with_char_string
234+
235+
!> Check whether a string starts with substring or not
236+
elemental function starts_with_string_string(string, substring) result(match)
237+
type(string_type), intent(in) :: string
238+
type(string_type), intent(in) :: substring
239+
logical :: match
240+
241+
match = starts_with(char(string), char(substring))
242+
243+
end function starts_with_string_string
244+
245+
246+
!> Check whether a string ends with substring or not
247+
pure function ends_with_char_char(string, substring) result(match)
248+
character(len=*), intent(in) :: string
249+
character(len=*), intent(in) :: substring
250+
logical :: match
251+
integer :: last, nsub
252+
253+
last = len(string)
254+
nsub = len(substring)
255+
if (last < nsub) then
256+
match = .false.
257+
return
258+
end if
259+
match = string(last-nsub+1:last) == substring
260+
261+
end function ends_with_char_char
262+
263+
!> Check whether a string ends with substring or not
264+
elemental function ends_with_string_char(string, substring) result(match)
265+
type(string_type), intent(in) :: string
266+
character(len=*), intent(in) :: substring
267+
logical :: match
268+
269+
match = ends_with(char(string), substring)
270+
271+
end function ends_with_string_char
272+
273+
!> Check whether a string ends with substring or not
274+
elemental function ends_with_char_string(string, substring) result(match)
275+
character(len=*), intent(in) :: string
276+
type(string_type), intent(in) :: substring
277+
logical :: match
278+
279+
match = ends_with(string, char(substring))
280+
281+
end function ends_with_char_string
282+
283+
!> Check whether a string ends with substring or not
284+
elemental function ends_with_string_string(string, substring) result(match)
285+
type(string_type), intent(in) :: string
286+
type(string_type), intent(in) :: substring
287+
logical :: match
288+
289+
match = ends_with(char(string), char(substring))
290+
291+
end function ends_with_string_string
292+
293+
176294
end module stdlib_strings

src/tests/string/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
ADDTEST(string_assignment)
22
ADDTEST(string_operator)
33
ADDTEST(string_intrinsic)
4+
ADDTEST(string_match)
45
ADDTEST(string_derivedtype_io)
56
ADDTEST(string_functions)
67
ADDTEST(string_strip_chomp)

src/tests/string/Makefile.manual

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ PROGS_SRC = test_string_assignment.f90 \
22
test_string_derivedtype_io.f90 \
33
test_string_functions.f90 \
44
test_string_intrinsic.f90 \
5+
test_string_match.f90 \
56
test_string_operator.f90 \
67
test_string_strip_chomp.f90
78

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
! SPDX-Identifier: MIT
2+
module test_match
3+
use stdlib_ascii, only : reverse
4+
use stdlib_error, only : check
5+
use stdlib_strings, only : starts_with, ends_with
6+
use stdlib_string_type, only : string_type
7+
implicit none
8+
9+
contains
10+
11+
subroutine check_starts_with(string, substring)
12+
character(len=*), intent(in) :: string
13+
character(len=*), intent(in) :: substring
14+
logical :: match
15+
character(len=:), allocatable :: message
16+
17+
match = index(string, substring) == 1
18+
if (match) then
19+
message = "Failed to recognize that '"//string//"' starts with '"//substring//"'"
20+
else
21+
message = "Incorrectly found that '"//string//"' starts with '"//substring//"'"
22+
end if
23+
24+
call check(starts_with(string, substring) .eqv. match, message)
25+
call check(starts_with(string_type(string), substring) .eqv. match, message)
26+
call check(starts_with(string, string_type(substring)) .eqv. match, message)
27+
call check(starts_with(string_type(string), string_type(substring)) .eqv. match, message)
28+
end subroutine check_starts_with
29+
30+
subroutine test_starts_with
31+
call check_starts_with("pattern", "pat")
32+
call check_starts_with("pat", "pattern")
33+
call check_starts_with("pattern", "ern")
34+
call check_starts_with("ern", "pattern")
35+
end subroutine test_starts_with
36+
37+
subroutine check_ends_with(string, substring)
38+
character(len=*), intent(in) :: string
39+
character(len=*), intent(in) :: substring
40+
logical :: match
41+
character(len=:), allocatable :: message
42+
43+
match = index(reverse(string), reverse(substring)) == 1
44+
if (match) then
45+
message = "Failed to recognize that '"//string//"' ends with '"//substring//"'"
46+
else
47+
message = "Incorrectly found that '"//string//"' ends with '"//substring//"'"
48+
end if
49+
50+
call check(ends_with(string, substring) .eqv. match, message)
51+
call check(ends_with(string_type(string), substring) .eqv. match, message)
52+
call check(ends_with(string, string_type(substring)) .eqv. match, message)
53+
call check(ends_with(string_type(string), string_type(substring)) .eqv. match, message)
54+
end subroutine check_ends_with
55+
56+
subroutine test_ends_with
57+
call check_ends_with("pattern", "pat")
58+
call check_ends_with("pat", "pattern")
59+
call check_ends_with("pattern", "ern")
60+
call check_ends_with("ern", "pattern")
61+
end subroutine test_ends_with
62+
63+
end module test_match
64+
65+
program tester
66+
use test_match
67+
implicit none
68+
69+
call test_starts_with
70+
call test_ends_with
71+
72+
end program tester

0 commit comments

Comments
 (0)