Skip to content

Commit 3e197e9

Browse files
authored
Merge pull request #39 from sourceryinstitute/add-file-abstraction
Feature: add `file_t` file abstraction
2 parents 3cb7533 + e82d270 commit 3e197e9

File tree

6 files changed

+216
-7
lines changed

6 files changed

+216
-7
lines changed

src/file_m.f90

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module file_m
2+
!! A representation of a file as an object
3+
use string_m, only : string_t
4+
5+
private
6+
public :: file_t
7+
8+
type file_t
9+
private
10+
type(string_t), allocatable :: lines_(:)
11+
contains
12+
procedure :: lines
13+
procedure :: write_lines
14+
end type
15+
16+
interface file_t
17+
18+
impure elemental module function read_lines(file_name) result(file_object)
19+
implicit none
20+
type(string_t), intent(in) :: file_name
21+
type(file_t) file_object
22+
end function
23+
24+
pure module function construct(lines) result(file_object)
25+
implicit none
26+
type(string_t), intent(in), allocatable :: lines(:)
27+
type(file_t) file_object
28+
end function
29+
30+
end interface
31+
32+
interface
33+
34+
pure module function lines(self) result(my_lines)
35+
implicit none
36+
class(file_t), intent(in) :: self
37+
type(string_t), allocatable :: my_lines(:)
38+
end function
39+
40+
impure elemental module subroutine write_lines(self, file_name)
41+
implicit none
42+
class(file_t), intent(in) :: self
43+
type(string_t), intent(in), optional :: file_name
44+
end subroutine
45+
46+
end interface
47+
48+
end module file_m

src/file_s.f90

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
submodule(file_m) file_s
2+
use iso_fortran_env, only : iostat_end, iostat_eor, output_unit
3+
use assert_m, only : assert
4+
implicit none
5+
6+
contains
7+
8+
module procedure construct
9+
file_object%lines_ = lines
10+
end procedure
11+
12+
module procedure write_lines
13+
14+
integer file_unit, io_status, l
15+
16+
call assert(allocated(self%lines_), "file_t%write_lines: allocated(self%lines_)")
17+
18+
if (present(file_name)) then
19+
open(newunit=file_unit, file=file_name%string(), form='formatted', status='unknown', iostat=io_status, action='write')
20+
call assert(io_status==0,"write_lines: io_status==0 after 'open' statement", file_name%string())
21+
else
22+
file_unit = output_unit
23+
end if
24+
25+
do l = 1, size(self%lines_)
26+
write(file_unit, *) self%lines_(l)%string()
27+
end do
28+
29+
if (present(file_name)) close(file_unit)
30+
end procedure
31+
32+
module procedure read_lines
33+
34+
integer io_status, file_unit, line_num
35+
character(len=:), allocatable :: line
36+
integer, parameter :: max_message_length=128
37+
character(len=max_message_length) error_message
38+
integer, allocatable :: lengths(:)
39+
40+
open(newunit=file_unit, file=file_name%string(), form='formatted', status='old', iostat=io_status, action='read')
41+
call assert(io_status==0,"read_lines: io_status==0 after 'open' statement", file_name%string())
42+
43+
lengths = line_lengths(file_unit)
44+
45+
associate(num_lines => size(lengths))
46+
47+
allocate(file_object%lines_(num_lines))
48+
49+
do line_num = 1, num_lines
50+
allocate(character(len=lengths(line_num)) :: line)
51+
read(file_unit, '(a)', iostat=io_status, iomsg=error_message) line
52+
call assert(io_status==0,"read_lines: io_status==0 after line read", error_message)
53+
file_object%lines_(line_num) = string_t(line)
54+
deallocate(line)
55+
end do
56+
57+
end associate
58+
59+
close(file_unit)
60+
61+
contains
62+
63+
function line_count(file_unit) result(num_lines)
64+
integer, intent(in) :: file_unit
65+
integer num_lines
66+
67+
rewind(file_unit)
68+
num_lines = 0
69+
do
70+
read(file_unit, *, iostat=io_status)
71+
if (io_status==iostat_end) exit
72+
num_lines = num_lines + 1
73+
end do
74+
rewind(file_unit)
75+
end function
76+
77+
function line_lengths(file_unit) result(lengths)
78+
integer, intent(in) :: file_unit
79+
integer, allocatable :: lengths(:)
80+
integer io_status
81+
character(len=1) c
82+
83+
associate(num_lines => line_count(file_unit))
84+
85+
allocate(lengths(num_lines), source = 0)
86+
rewind(file_unit)
87+
88+
do line_num = 1, num_lines
89+
do
90+
read(file_unit, '(a)', advance='no', iostat=io_status, iomsg=error_message) c
91+
if (io_status==iostat_eor .or. io_status==iostat_end) exit
92+
lengths(line_num) = lengths(line_num) + 1
93+
end do
94+
end do
95+
96+
rewind(file_unit)
97+
98+
end associate
99+
end function
100+
101+
end procedure
102+
103+
module procedure lines
104+
my_lines = self%lines_
105+
end procedure
106+
107+
end submodule file_s

src/string_m.f90

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module string_m
1010
character(len=:), allocatable :: string_
1111
contains
1212
procedure :: string
13+
procedure :: is_allocated
1314
end type
1415

1516
interface string_t
@@ -35,7 +36,13 @@ pure module function array_of_strings(delimited_strings, delimiter) result(strin
3536
character(len=*), intent(in) :: delimited_strings, delimiter
3637
type(string_t), allocatable :: strings_array(:)
3738
end function
38-
39+
40+
elemental module function is_allocated(self) result(string_allocated)
41+
implicit none
42+
class(string_t), intent(in) :: self
43+
logical string_allocated
44+
end function
45+
3946
end interface
4047

4148
end module string_m

src/string_s.f90

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33

44
contains
55

6-
module procedure construct
7-
new_string%string_ = string
8-
end procedure
6+
module procedure construct
7+
new_string%string_ = string
8+
end procedure
9+
10+
module procedure string
11+
raw_string = self%string_
12+
end procedure
913

10-
module procedure string
11-
raw_string = self%string_
12-
end procedure
14+
module procedure is_allocated
15+
string_allocated = allocated(self%string_)
16+
end procedure
1317

1418
module procedure array_of_strings
1519
character(len=:), allocatable :: remainder, next_string

test/main.f90

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ program main
55
use formats_test, only : formats_test_t
66
use test_result_test, only : test_result_test_t
77
use command_line_test, only : command_line_test_t
8+
use string_test, only : string_test_t
89
implicit none
910

1011
type(collectives_test_t) collectives_test
@@ -13,6 +14,7 @@ program main
1314
type(object_test_t) object_test
1415
type(test_result_test_t) test_result_test
1516
type(command_line_test_t) command_line_test
17+
type(string_test_t) string_test
1618

1719
integer :: passes=0, tests=0
1820

@@ -22,6 +24,7 @@ program main
2224
call formats_test%report(passes, tests)
2325
call test_result_test%report(passes, tests)
2426
call command_line_test%report(passes, tests)
27+
call string_test%report(passes, tests)
2528

2629
print *
2730
print *,"_________ In total, ",passes," of ",tests, " tests pass. _________"

test/string_test.f90

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module string_test
2+
use test_m, only : test_t, test_result_t
3+
use string_m, only : string_t
4+
implicit none
5+
6+
private
7+
public :: string_test_t
8+
9+
type, extends(test_t) :: string_test_t
10+
contains
11+
procedure, nopass :: subject
12+
procedure, nopass :: results
13+
end type
14+
15+
contains
16+
17+
pure function subject() result(specimen)
18+
character(len=:), allocatable :: specimen
19+
specimen = "The string_t type"
20+
end function
21+
22+
function results() result(test_results)
23+
type(test_result_t), allocatable :: test_results(:)
24+
25+
test_results = [ &
26+
test_result_t("is_allocated() result .true. if & only if the string_t component(s) is/are allocated", check_allocation()) &
27+
]
28+
end function
29+
30+
pure function check_allocation() result(passed)
31+
type(string_t) :: scalar_not_allocated, scalar_allocated, array_allocated(2), array_not_allocated(2)
32+
logical passed
33+
34+
scalar_allocated = string_t("")
35+
array_allocated = [string_t("yada yada"), string_t("blah blah blah")]
36+
passed = (.not. any([scalar_not_allocated%is_allocated(), array_not_allocated%is_allocated()])) .and. &
37+
(all([scalar_allocated%is_allocated(), array_allocated%is_allocated()]))
38+
end function
39+
40+
end module string_test

0 commit comments

Comments
 (0)