Skip to content

Commit 08ec3ff

Browse files
authored
introduce join_spaced functionality (#3)
2 parents 0d7dfb6 + 6945ed3 commit 08ec3ff

File tree

3 files changed

+171
-13
lines changed

3 files changed

+171
-13
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ tokens = split('whatever ',success=success)
3333
! With complete error flag
3434
tokens = split('whatever ',error)
3535
print *, 'error message=',error%string
36-
3736
```
3837

3938
And the `shlex` function has the same API, but returns a list of `type(shlex_token)`s instead of an allocatable character array.
@@ -55,8 +54,24 @@ tokens = shlex('whatever ',success=success)
5554
tokens = shlex('whatever ',error)
5655
print *, 'error message=',error%string
5756
57+
---
58+
59+
## Version 1.1.0 - `join_spaced` to combine spaced compiler flags
60+
61+
Starting with version **1.1.0**, a new high-level interface is available:
62+
63+
```fortran
64+
tokens = split_joined_bool('gfortran -I /include -L /lib -lm', join_spaced=.true., success=success)
5865
```
5966

67+
When the second argument (`join_spaced`) is `.true.`, `split_joined_bool()` will:
68+
- combine spaced flags like `-I /path` into `-I/path`
69+
- work for any single-letter flags (e.g., `-I`, `-L`, `-D`) if the next token does *not* begin with `-`
70+
- still respect quotes and shell-like rules for escaping
71+
72+
This is useful for parsing compiler and linker flags where `-I`, `-L`, etc. may be followed by a separate token due to quoting or formatting.
73+
74+
---
6075

6176
## License
6277

src/shlex_module.f90

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ module shlex_module
2424
public :: shlex
2525
interface shlex
2626
module procedure shlex_bool
27-
module procedure shlex_error
27+
module procedure shlex_error
2828
end interface
2929

3030
! Split: return split strings
3131
public :: split
3232
interface split
3333
module procedure split_bool
3434
module procedure split_error
35+
module procedure split_joined_bool
36+
module procedure split_joined_error
3537
end interface
3638

3739
! Turn on verbosity for debugging
@@ -156,22 +158,100 @@ function split_error(pattern,error) result(list)
156158

157159
type(shlex_token), allocatable :: tokens(:)
158160

159-
integer :: n,maxlen,i,l
160-
161161
tokens = shlex(pattern,error)
162+
call tokens_to_strings(tokens,list)
162163

164+
end function split_error
165+
166+
! Convert a list of tokens to strings
167+
pure subroutine tokens_to_strings(tokens,list)
168+
type(shlex_token), optional, intent(in) :: tokens(:)
169+
character(kind=SCK,len=:), allocatable, intent(out) :: list(:)
170+
171+
integer :: n,maxlen,i
172+
163173
n = size(tokens)
164174
maxlen = 0
175+
165176
do i=1,n
166177
maxlen = max(maxlen,len(tokens(i)%string))
167178
end do
168179

169180
allocate(character(kind=SCK,len=maxlen) :: list(n))
170181
do i=1,n
171182
list(i) = tokens(i)%string
172-
end do
183+
end do
184+
185+
end subroutine tokens_to_strings
186+
187+
! High level interface: also join spaced flags like -I /path -> -I/path
188+
function split_joined_bool(pattern, join_spaced, success) result(list)
189+
character(*), intent(in) :: pattern
190+
logical, intent(in) :: join_spaced
191+
logical, intent(out) :: success
192+
character(kind=SCK,len=:), allocatable :: list(:)
193+
type(shlex_token) :: error
173194

174-
end function split_error
195+
list = split_joined_error(pattern, join_spaced, error)
196+
success = error%type==NO_ERROR
197+
198+
end function split_joined_bool
199+
200+
! High level interface: also join spaced flags like -I /path -> -I/path
201+
function split_joined_error(pattern, join_spaced, error) result(list)
202+
character(*), intent(in) :: pattern
203+
type(shlex_token), intent(out) :: error
204+
logical, intent(in) :: join_spaced
205+
character(kind=SCK,len=:), allocatable :: list(:)
206+
207+
integer :: i, n, count
208+
type(shlex_token) :: tok, next_tok
209+
type(shlex_token), allocatable :: raw(:),joined(:)
210+
211+
raw = shlex(pattern,error)
212+
213+
if (error%type/=NO_ERROR .or. .not. join_spaced) then
214+
215+
call tokens_to_strings(raw,list)
216+
217+
else
218+
219+
n = size(raw)
220+
221+
allocate(joined(n))
222+
count = 0
223+
i = 1
224+
225+
old_tokens: do while (i <= n)
226+
tok = raw(i)
227+
228+
if (len_trim(tok%string)==2) then
229+
230+
if (tok%string(1:1) == '-' .and. &
231+
(tok%string(2:2) >= 'A' .and. tok%string(2:2) <= 'Z' .or. tok%string(2:2) >= 'a' .and. tok%string(2:2) <= 'z')) then
232+
if (i + 1 <= n) then
233+
next_tok = raw(i + 1)
234+
if (.not. (len_trim(next_tok%string) >= 1 .and. next_tok%string(1:1) == '-')) then
235+
count = count + 1
236+
joined(count) = shlex_token(TOKEN_WORD,trim(tok%string)//trim(next_tok%string))
237+
i = i + 2
238+
cycle old_tokens
239+
end if
240+
end if
241+
endif
242+
243+
end if
244+
245+
count = count + 1
246+
joined(count) = tok
247+
i = i + 1
248+
end do old_tokens
249+
250+
call tokens_to_strings(joined(:count),list)
251+
252+
end if
253+
254+
end function split_joined_error
175255

176256
! High level interface: return a list of tokens
177257
function shlex_bool(pattern,success) result(list)

test/shlex_test.f90

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ program shlex_tests
2121
integer :: nfailed = 0
2222
integer :: npassed = 0
2323

24-
integer :: i,length
25-
logical :: valid
26-
character(len=30) :: pattern,str
27-
2824
call add_test(test_1())
2925
call add_test(test_2())
3026
call add_test(test_3())
3127
call add_test(test_4())
3228
call add_test(test_5())
29+
call add_test(test_joined_1())
30+
call add_test(test_joined_2())
31+
call add_test(test_joined_3())
3332

3433

3534
if (nfailed<=0) then
@@ -129,9 +128,6 @@ logical function test_4() result(success)
129128
end do
130129
end function test_4
131130

132-
133-
134-
135131
logical function test_5() result(success)
136132

137133
character(*), parameter :: string = &
@@ -159,5 +155,72 @@ logical function test_5() result(success)
159155
end do
160156
end function test_5
161157

158+
! fpm case input
159+
logical function test_joined_1() result(success)
160+
161+
character(*), parameter :: string = &
162+
'-I/path/to/include -I /test -I"/path/to/include with spaces" -I "spaces here too" -L/path/to/lib -lmylib -O2 -g -Wall'
163+
164+
character(*), parameter :: results(*) = [character(40) :: &
165+
'-I/path/to/include', '-I/test', '-I/path/to/include with spaces', '-Ispaces here too', &
166+
'-L/path/to/lib', '-lmylib', '-O2', '-g', '-Wall']
167+
168+
integer :: i
169+
character(len=:), allocatable :: tokens(:)
170+
171+
tokens = split(string, join_spaced=.true., success=success)
172+
if (.not.success) return
173+
success = size(tokens) == size(results)
174+
175+
do i = 1, size(tokens)
176+
success = tokens(i) == trim(results(i))
177+
if (.not.success) print *, 'token=', tokens(i), ' expected=', results(i)
178+
if (.not.success) return
179+
end do
180+
181+
end function test_joined_1
182+
183+
! mixed spacing, no quoted flags
184+
logical function test_joined_2() result(success)
185+
186+
character(*), parameter :: string = '-I include -L lib -O3 -Wall -lm'
187+
character(*), parameter :: results(*) = [character(10) :: '-Iinclude', '-Llib', '-O3', '-Wall', '-lm']
188+
189+
integer :: i
190+
character(len=:), allocatable :: tokens(:)
191+
192+
tokens = split(string, .true., success)
193+
if (.not.success) return
194+
success = size(tokens) == size(results)
195+
196+
do i = 1, size(tokens)
197+
success = tokens(i) == trim(results(i))
198+
if (.not.success) print *, 'token=', tokens(i), ' expected=', results(i)
199+
if (.not.success) return
200+
end do
201+
202+
end function test_joined_2
203+
204+
! ensure flags with attached args are left untouched
205+
logical function test_joined_3() result(success)
206+
207+
character(*), parameter :: string = '-I/path -I /spaced -DMACRO=1 -lfoo'
208+
character(*), parameter :: results(*) = [character(20) :: '-I/path', '-I/spaced', '-DMACRO=1', '-lfoo']
209+
210+
integer :: i
211+
character(len=:), allocatable :: tokens(:)
212+
213+
tokens = split(string, .true., success)
214+
if (.not.success) return
215+
success = size(tokens) == size(results)
216+
217+
do i = 1, size(tokens)
218+
success = tokens(i) == trim(results(i))
219+
if (.not.success) print *, 'token=', tokens(i), ' expected=', results(i)
220+
if (.not.success) return
221+
end do
222+
223+
end function test_joined_3
224+
162225

163226
end program shlex_tests

0 commit comments

Comments
 (0)