Skip to content

Commit f857482

Browse files
authored
Merge branch 'master' into dev-optval
2 parents 274a2bb + 7a6108e commit f857482

31 files changed

+525
-84
lines changed

.github/workflows/CI.yml

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ env:
77
CMAKE_BUILD_PARALLEL_LEVEL: "2" # 2 cores on each GHA VM, enable parallel builds
88
CTEST_OUTPUT_ON_FAILURE: "ON" # This way we don't need a flag to ctest
99
CTEST_PARALLEL_LEVEL: "2"
10+
CTEST_TIME_TIMEOUT: "5" # some failures hang forever
1011
HOMEBREW_NO_ANALYTICS: "ON" # Make Homebrew installation a little quicker
1112
HOMEBREW_NO_AUTO_UPDATE: "ON"
1213
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: "ON"
@@ -55,10 +56,15 @@ jobs:
5556
run: cmake -Wdev -DCMAKE_BUILD_TYPE=Release -S . -B build
5657

5758
- name: Build and compile
58-
run: cmake --build build || cmake --build build --verbose --parallel 1
59+
run: cmake --build build
60+
61+
- name: catch build fail
62+
run: cmake --build build --verbose --parallel 1
63+
if: failure()
5964

6065
- name: test
61-
run: cmake --build build --target test
66+
run: ctest --parallel --output-on-failure
67+
working-directory: build
6268

6369
- name: Test in-tree builds
6470
if: contains( matrix.gcc_v, '9') # Only test one compiler on each platform

.github/workflows/ci_windows.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on: [push, pull_request]
44

55
env:
66
CI: "ON"
7+
CTEST_TIME_TIMEOUT: "5" # some failures hang forever
78

89
jobs:
910
Build:
@@ -24,7 +25,8 @@ jobs:
2425
- name: CMake build
2526
run: cmake --build build --parallel
2627

27-
- run: cmake --build build --verbose --parallel 1
28+
- name: catch build fail
29+
run: cmake --build build --verbose --parallel 1
2830
if: failure()
2931

3032
- name: CTest

CMakeLists.txt

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
cmake_minimum_required(VERSION 3.5.0)
1+
cmake_minimum_required(VERSION 3.14.0)
22
project(stdlib Fortran)
33
enable_testing()
44

55
# this avoids stdlib and projects using stdlib from having to introspect stdlib's directory structure
6+
# FIXME: this eventually needs to be handled more precisely, as this spills all .mod/.smod into one directory
7+
# and thereby can clash if module/submodule names are the same in different parts of library
68
set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR})
79

8-
# compiler feature checks
10+
# --- compiler options
11+
if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU)
12+
add_compile_options(-fimplicit-none)
13+
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Intel)
14+
add_compile_options(-warn declarations)
15+
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL PGI)
16+
add_compile_options(-Mdclchk)
17+
endif()
18+
19+
# --- compiler feature checks
920
include(CheckFortranSourceCompiles)
21+
include(CheckFortranSourceRuns)
1022
check_fortran_source_compiles("error stop i; end" f18errorstop SRC_EXT f90)
23+
check_fortran_source_runs("use, intrinsic :: iso_fortran_env, only : real128; real(real128) :: x; x = x+1; end" f03real128)
1124

1225
add_subdirectory(src)

src/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(SRC
33
stdlib_experimental_io.f90
44
stdlib_experimental_error.f90
55
stdlib_experimental_optval.f90
6+
stdlib_experimental_kinds.f90
67
)
78

89
add_library(fortran_stdlib ${SRC})

src/Makefile.manual

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ SRC = stdlib_experimental_ascii.f90 \
22
stdlib_experimental_error.f90 \
33
stdlib_experimental_io.f90 \
44
stdlib_experimental_optval.f90 \
5+
stdlib_experimental_kinds.f90 \
56
f18estop.f90
67

78
LIB = libstdlib.a
@@ -28,3 +29,8 @@ clean:
2829

2930
# Fortran module dependencies
3031
f18estop.o: stdlib_experimental_error.o
32+
stdlib_experimental_io.o: \
33+
stdlib_experimental_error.o \
34+
stdlib_experimental_optval.o \
35+
stdlib_experimental_kinds.o
36+
stdlib_experimental_optval.o: stdlib_experimental_kinds.o

src/f08estop.f90

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
submodule (stdlib_experimental_error) estop
22

3+
implicit none
4+
35
contains
46

57
module procedure error_stop

src/f18estop.f90

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
submodule (stdlib_experimental_error) estop
22

3+
implicit none
4+
35
contains
46

57
module procedure error_stop

src/stdlib_experimental_io.f90

+144-19
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
module stdlib_experimental_io
2-
use iso_fortran_env, only: sp=>real32, dp=>real64, qp=>real128
2+
use stdlib_experimental_kinds, only: sp, dp, qp
3+
use stdlib_experimental_error, only: error_stop
4+
use stdlib_experimental_optval, only: optval
5+
use stdlib_experimental_ascii, only: is_blank
36
implicit none
47
private
5-
public :: loadtxt, savetxt
8+
! Public API
9+
public :: loadtxt, savetxt, open
10+
11+
! Private API that is exposed so that we can test it in tests
12+
public :: parse_mode
13+
614

715
interface loadtxt
816
module procedure sloadtxt
@@ -46,7 +54,7 @@ subroutine sloadtxt(filename, d)
4654
integer :: s
4755
integer :: nrow,ncol,i
4856

49-
open(newunit=s, file=filename, status="old", action="read")
57+
s = open(filename)
5058

5159
! determine number of columns
5260
ncol = number_of_columns(s)
@@ -89,7 +97,7 @@ subroutine dloadtxt(filename, d)
8997
integer :: s
9098
integer :: nrow,ncol,i
9199

92-
open(newunit=s, file=filename, status="old", action="read")
100+
s = open(filename)
93101

94102
! determine number of columns
95103
ncol = number_of_columns(s)
@@ -132,7 +140,7 @@ subroutine qloadtxt(filename, d)
132140
integer :: s
133141
integer :: nrow,ncol,i
134142

135-
open(newunit=s, file=filename, status="old", action="read")
143+
s = open(filename)
136144

137145
! determine number of columns
138146
ncol = number_of_columns(s)
@@ -164,7 +172,7 @@ subroutine ssavetxt(filename, d)
164172
! call savetxt("log.txt", data)
165173

166174
integer :: s, i
167-
open(newunit=s, file=filename, status="replace", action="write")
175+
s = open(filename, "w")
168176
do i = 1, size(d, 1)
169177
write(s, *) d(i, :)
170178
end do
@@ -187,7 +195,7 @@ subroutine dsavetxt(filename, d)
187195
! call savetxt("log.txt", data)
188196

189197
integer :: s, i
190-
open(newunit=s, file=filename, status="replace", action="write")
198+
s = open(filename, "w")
191199
do i = 1, size(d, 1)
192200
write(s, *) d(i, :)
193201
end do
@@ -210,9 +218,12 @@ subroutine qsavetxt(filename, d)
210218
! call savetxt("log.txt", data)
211219

212220
integer :: s, i
213-
open(newunit=s, file=filename, status="replace", action="write")
221+
character(len=14) :: format_string
222+
223+
write(format_string, '(a1,i06,a7)') '(', size(d, 2), 'f40.34)'
224+
s = open(filename, "w")
214225
do i = 1, size(d, 1)
215-
write(s, *) d(i, :)
226+
write(s, format_string) d(i, :)
216227
end do
217228
close(s)
218229
end subroutine
@@ -224,16 +235,16 @@ integer function number_of_columns(s)
224235

225236
integer :: ios
226237
character :: c
227-
logical :: lastwhite
238+
logical :: lastblank
228239

229240
rewind(s)
230241
number_of_columns = 0
231-
lastwhite = .true.
242+
lastblank = .true.
232243
do
233244
read(s, '(a)', advance='no', iostat=ios) c
234245
if (ios /= 0) exit
235-
if (lastwhite .and. .not. whitechar(c)) number_of_columns = number_of_columns + 1
236-
lastwhite = whitechar(c)
246+
if (lastblank .and. .not. is_blank(c)) number_of_columns = number_of_columns + 1
247+
lastblank = is_blank(c)
237248
end do
238249
rewind(s)
239250

@@ -258,14 +269,128 @@ integer function number_of_rows_numeric(s)
258269

259270
end function
260271

261-
logical function whitechar(char) ! white character
262-
! returns .true. if char is space (32) or tab (9), .false. otherwise
263-
character, intent(in) :: char
264-
if (iachar(char) == 32 .or. iachar(char) == 9) then
265-
whitechar = .true.
272+
integer function open(filename, mode, iostat) result(u)
273+
! Open a file
274+
!
275+
! To open a file to read:
276+
!
277+
! u = open("somefile.txt") # The default `mode` is "rt"
278+
! u = open("somefile.txt", "r")
279+
!
280+
! To open a file to write:
281+
!
282+
! u = open("somefile.txt", "w")
283+
284+
! To append to the end of the file if it exists:
285+
!
286+
! u = open("somefile.txt", "a")
287+
288+
character(*), intent(in) :: filename
289+
character(*), intent(in), optional :: mode
290+
integer, intent(out), optional :: iostat
291+
292+
integer :: io_
293+
character(3) :: mode_
294+
character(:),allocatable :: action_, position_, status_, access_, form_
295+
296+
297+
mode_ = parse_mode(optval(mode, ""))
298+
299+
select case (mode_(1:2))
300+
case('r')
301+
action_='read'
302+
position_='asis'
303+
status_='old'
304+
case('w')
305+
action_='write'
306+
position_='asis'
307+
status_='replace'
308+
case('a')
309+
action_='write'
310+
position_='append'
311+
status_='old'
312+
case('x')
313+
action_='write'
314+
position_='asis'
315+
status_='new'
316+
case('r+')
317+
action_='readwrite'
318+
position_='asis'
319+
status_='old'
320+
case('w+')
321+
action_='readwrite'
322+
position_='asis'
323+
status_='replace'
324+
case('a+')
325+
action_='readwrite'
326+
position_='append'
327+
status_='old'
328+
case('x+')
329+
action_='readwrite'
330+
position_='asis'
331+
status_='new'
332+
case default
333+
call error_stop("Unsupported mode: "//mode_(1:2))
334+
end select
335+
336+
select case (mode_(3:3))
337+
case('t')
338+
form_='formatted'
339+
case('b')
340+
form_='unformatted'
341+
case default
342+
call error_stop("Unsupported mode: "//mode_(3:3))
343+
end select
344+
345+
access_ = 'stream'
346+
347+
if (present(iostat)) then
348+
open(newunit=u, file=filename, &
349+
action = action_, position = position_, status = status_, &
350+
access = access_, form = form_, &
351+
iostat = iostat)
266352
else
267-
whitechar = .false.
353+
open(newunit=u, file=filename, &
354+
action = action_, position = position_, status = status_, &
355+
access = access_, form = form_)
268356
end if
357+
358+
end function
359+
360+
character(3) function parse_mode(mode) result(mode_)
361+
character(*), intent(in) :: mode
362+
363+
integer :: i
364+
character(:),allocatable :: a
365+
logical :: lfirst(3)
366+
367+
mode_ = 'r t'
368+
369+
if (len_trim(mode) == 0) return
370+
a=trim(adjustl(mode))
371+
372+
lfirst = .true.
373+
do i=1,len(a)
374+
if (lfirst(1) &
375+
.and. (a(i:i) == 'r' .or. a(i:i) == 'w' .or. a(i:i) == 'a' .or. a(i:i) == 'x') &
376+
) then
377+
mode_(1:1) = a(i:i)
378+
lfirst(1)=.false.
379+
else if (lfirst(2) .and. a(i:i) == '+') then
380+
mode_(2:2) = a(i:i)
381+
lfirst(2)=.false.
382+
else if (lfirst(3) .and. (a(i:i) == 't' .or. a(i:i) == 'b')) then
383+
mode_(3:3) = a(i:i)
384+
lfirst(3)=.false.
385+
else if (a(i:i) == ' ') then
386+
cycle
387+
else if(any(.not.lfirst)) then
388+
call error_stop("Wrong mode: "//trim(a))
389+
else
390+
call error_stop("Wrong character: "//a(i:i))
391+
endif
392+
end do
393+
269394
end function
270395

271396
end module

src/stdlib_experimental_kinds.f90

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module stdlib_experimental_kinds
2+
use iso_fortran_env, only: sp=>real32, dp=>real64, qp=>real128
3+
use iso_fortran_env, only: int8, int16, int32, int64
4+
! If we decide later to use iso_fortran_env instead of iso_fortran_env:
5+
!use iso_c_binding, only: sp=>c_float, dp=>c_double, qp=>c_float128
6+
!use iso_c_binding, only: int8=>c_int8_t, int16=>c_int16_t, int32=>c_int32_t, int64=>c_int64_t
7+
implicit none
8+
private
9+
public sp, dp, qp, int8, int16, int32, int64
10+
end module

src/stdlib_experimental_optval.f90

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module stdlib_experimental_optval
88
!!
99
!! It is an error to call `optval` with a single actual argument.
1010
!!
11-
use iso_fortran_env, only: sp => real32, dp => real64, qp => real128, int8, int16, int32, int64
11+
use stdlib_experimental_kinds, only: sp, dp, qp, int8, int16, int32, int64
1212
implicit none
1313

1414

src/tests/CMakeLists.txt

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
macro(ADDTEST name)
2+
add_executable(test_${name} test_${name}.f90)
3+
target_link_libraries(test_${name} fortran_stdlib)
4+
add_test(NAME ${name}
5+
COMMAND $<TARGET_FILE:test_${name}> ${CMAKE_CURRENT_BINARY_DIR}
6+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
7+
endmacro(ADDTEST)
8+
19
add_subdirectory(ascii)
2-
add_subdirectory(loadtxt)
10+
add_subdirectory(io)
311
add_subdirectory(optval)
412

5-
add_executable(test_skip test_skip.f90)
6-
target_link_libraries(test_skip fortran_stdlib)
7-
add_test(NAME AlwaysSkip COMMAND $<TARGET_FILE:test_skip>)
8-
set_tests_properties(AlwaysSkip PROPERTIES SKIP_RETURN_CODE 77)
9-
10-
add_executable(test_fail test_fail.f90)
11-
target_link_libraries(test_fail fortran_stdlib)
12-
add_test(NAME AlwaysFail COMMAND $<TARGET_FILE:test_fail>)
13-
set_tests_properties(AlwaysFail PROPERTIES WILL_FAIL true)
13+
ADDTEST(always_skip)
14+
set_tests_properties(always_skip PROPERTIES SKIP_RETURN_CODE 77)
15+
ADDTEST(always_fail)
16+
set_tests_properties(always_fail PROPERTIES WILL_FAIL true)

0 commit comments

Comments
 (0)