Loading...
Searching...
No Matches
include.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_include Include
3!! Include file handling and resolution for the fpx Fortran preprocessor
4!!
5!! This module implements robust and standard-compliant processing of `#include` directives
6!! with full support for:
7!! - Both forms: `#include "file.h"` (local/user) and `#include <file.h>` (system)
8!! - Proper search order: quotes search source dir first, angle brackets skip source dir
9!! - Relative paths resolved against the directory of the parent source file
10!! - Search in user-defined include directories (`global%includedir`)
11!! - Search in system PATH environment variable directories
12!! - Fallback to current working directory
13!! - Proper error reporting with file name and line number context
14!! - Recursion safety through integration with the main preprocessor loop
15!! - Seamless integration via the abstract `preprocess` procedure pointer
16!!
17!! The routine correctly strips quotes or angle brackets, performs path resolution,
18!! checks file existence, opens the file, and recursively invokes the main preprocessing
19!! engine on the included content using the same macro environment.
20!!
21!! @note
22!! For `#include "file"`:
23!! 1. Directory of the parent source file
24!! 2. Directories specified by the -I option (`global%includedir`)
25!! 3. Directories in INCLUDE environment variable
26!! 4. Current working directory
27!!
28!! @note
29!! For `#include <file>`:
30!! 1. Directories specified by the -I option (`global%includedir`)
31!! 2. Directories in INCLUDE environment variable
32!! 3. Current working directory
33!!
34!! @section include_examples Examples
35!!
36!! 1. Include a local header from the same directory using quotes:
37!! @code{.f90}
38!! #include "config.h"
39!! !> fpx will look for ./config.h relative to the current source file first
40!! @endcode
41!!
42!! 2. Include a system header using angle brackets:
43!! @code{.f90}
44!! #include <stdlib.h>
45!! !> fpx will skip the source directory and search -I paths, then PATH
46!! @endcode
47!!
48!! 3. Using from the driver program (adding include paths):
49!! @code{.f90}
50!! global%includedir = ['/usr/include', './include', './headers']
51!! call preprocess('main.F90', 'main.f90')
52!! !> All #include <...> will search these directories in order
53!! @endcode
54!!
55!! 4. Verbose error reporting when a file is not found:
56!! @code{.txt}
57!! $ fpx -v src/utils.F90
58!! Error: Cannot find include file 'missing.h' at src/utils.F90:27
59!! @endcode
60module fpx_include
61 use iso_fortran_env, only : iostat_end
62 use fpx_constants
63 use fpx_logging
64 use fpx_path
65 use fpx_string
66 use fpx_macro
67 use fpx_global
68 use fpx_context
69
70 implicit none; private
71
72 public :: handle_include
73
74 ! Include directive types
75 integer, parameter, private :: INCLUDE_TYPE_SYSTEM = 1 ! < >
76 integer, parameter, private :: INCLUDE_TYPE_LOCAL = 2 ! " "
77#ifdef _WIN32
78 integer, parameter, private :: MAX_PATH_LEN = 256
79#else
80 integer, parameter, private :: MAX_PATH_LEN = 4096
81#endif
82
83contains
84
85 !> Process a #include directive encountered during preprocessing
86 !! Resolves the include file name (quoted or angle-bracketed), searches for the file
87 !! using standard C preprocessor rules:
88 !! - Quoted includes search: parent directory, -I paths, PATH, cwd
89 !! - Angle bracket includes search: -I paths, PATH, cwd (skips parent directory)
90 !! Opens the file and recursively preprocesses its contents into the output unit.
91 !!
92 !! @param[in] ctx Context line containing the #include directive
93 !! @param[in] ounit Output unit where preprocessed content is written
94 !! @param[in] preprocess Procedure pointer to the main line-by-line preprocessor
95 !! @param[inout] macros Current macro table (shared across recursion levels)
96 !! @param[in] token Usually 'include' – the directive keyword
97 !!
98 !! @b Remarks
99 !! @ingroup group_include
100 recursive subroutine handle_include(ctx, ounit, preprocess, macros, token)
101 type(context), intent(in) :: ctx
102 integer, intent(in) :: ounit
103 procedure(read_unit) :: preprocess
104 type(macro), allocatable, intent(inout) :: macros(:)
105 character(*), intent(in) :: token
106 !private
107 character(:), allocatable :: include_file
108 character(:), allocatable :: dir, ifile
109 character(:), allocatable :: sys_paths(:)
110 integer :: i, iunit, ierr, pos
111 integer :: include_type
112 logical :: exists
113
114 ! Extract the directory of the parent file
115 dir = dirpath(ctx%path)
116 ! Find the position after the #include token
117 pos = index(lowercase(ctx%content), token) + len(token)
118 include_file = trim(adjustl(ctx%content(pos:)))
119
120 ! Determine include type and extract filename
121 if (include_file(1:1) == '"') then
122 include_type = include_type_local
123 include_file = include_file(2:index(include_file(2:), '"'))
124 else if (include_file(1:1) == '<') then
125 include_type = include_type_system
126 include_file = include_file(2:index(include_file(2:), '>'))
127 else
128 ! Malformed include directive
129 call printf(render(diagnostic_report(level_error, &
130 message='Malformed #include directive', &
131 label=label_type('Filepath should either be delimited by "<...>" or "..."', index(ctx%content, include_file), &
132 len(include_file)), &
133 source=trim(ctx%path)), &
134 ctx%content, ctx%line))
135 return
136 end if
137
138 ! Handle absolute/rooted paths (same for both types)
139 ifile = include_file
140 if (is_rooted(ifile)) then
141 inquire(file=ifile, exist=exists)
142 if (exists) then
143 include_file = ifile
144 else
145 if (verbose) then
146 call printf(render(diagnostic_report(level_error, &
147 message='File not found', &
148 label=label_type('Cannot find include file ' // trim(include_file), index(ctx%content, include_file), &
149 len(include_file)), &
150 source=trim(ctx%path)), &
151 ctx%content, ctx%line))
152 return
153 end if
154 end if
155 else
156 ! Relative path - search according to include type
157 exists = .false.
158 ! For quoted includes (#include "file"), search parent directory first
159 ifile = join(dir, include_file)
160 if (include_type == include_type_local) then
161 ifile = join(dir, include_file)
162 inquire(file=ifile, exist=exists)
163 if (exists) then
164 include_file = ifile
165 end if
166 end if
167
168 ! If not found yet, search user-specified include directories (-I paths)
169 if (.not. exists .and. allocated(global%includedir)) then
170 do i = 1, size(global%includedir)
171 ifile = join(global%includedir(i), include_file)
172 inquire(file=ifile, exist=exists)
173 if (exists) then
174 include_file = ifile
175 exit
176 end if
177 end do
178 end if
179
180 ! If still not found, try the INCLUDE environmental variable
181 if (.not. exists) then
182 block
183 character(:), allocatable :: ipaths(:)
184
185 ipaths = get_system_paths()
186 do i = 1, size(ipaths)
187 ifile = join(ipaths(i), include_file)
188 inquire(file=ifile, exist=exists)
189 if (exists) then
190 include_file = ifile
191 end if
192 end do
193 end block
194 end if
195
196 ! If still not found, try current working directory as last resort
197 if (.not. exists) then
198 ifile = join(cwd(), include_file)
199 inquire(file=ifile, exist=exists)
200 if (exists) then
201 include_file = ifile
202 end if
203 end if
204
205 ! If file was not found anywhere, report error
206 if (.not. exists) then
207 call printf(render(diagnostic_report(level_error, &
208 message='File not found', &
209 label=label_type('Cannot find include file ' // trim(include_file), index(ctx%content, include_file), len(&
210 include_file)), &
211 source=trim(ctx%path)), &
212 ctx%content, ctx%line))
213 return
214 end if
215 end if
216
217 ! Open and preprocess the include file
218 open(newunit=iunit, file=include_file, status='old', action='read', iostat=ierr)
219 if (ierr /= 0) then
220 call printf(render(diagnostic_report(level_error, &
221 message='File not found', &
222 label=label_type('Cannot open include file ' // trim(include_file), index(ctx%content, include_file), len(&
223 include_file)), &
224 source=trim(ctx%path)), &
225 ctx%content, ctx%line))
226 return
227 end if
228
229 call preprocess(iunit, ounit, macros, .true.)
230 close(iunit)
231 end subroutine
232
233 !> Get system include paths from PATH environment variable
234 !! Returns an array of directory paths found in PATH
235 !! @return Array of path strings, empty if PATH not set
236 !!
237 !! @b Remarks
238 !! @ingroup group_include
239 function get_system_paths() result(paths)
240 character(:), allocatable :: paths(:)
241 !private
242 character(:), allocatable :: path_env, tmp(:)
243 integer :: lpath, i, n_paths, start_pos, end_pos, count
244 character(len=1) :: path_sep
245
246#ifdef _WIN32
247 path_sep = ';' ! Windows path separator
248#else
249 path_sep = ':' ! Unix/Linux/Mac path separator
250#endif
251
252 ! Get PATH environment variable length
253 call get_environment_variable('INCLUDE', length=lpath)
254 if (lpath <= 0) then
255 allocate(character(len=0) :: paths(0)); return
256 end if
257
258 ! Allocate and retrieve PATH value
259 allocate(character(len=lpath) :: path_env)
260 call get_environment_variable('INCLUDE', value=path_env)
261
262 ! Count number of paths (number of separators + 1)
263 n_paths = 1
264 do i = 1, len(path_env)
265 if (path_env(i:i) == path_sep) n_paths = n_paths + 1
266 end do
267
268 ! Allocate temporary array with maximum size
269 allocate(character(len=MAX_PATH_LEN) :: tmp(n_paths))
270
271 ! Split INCLUDE into individual directories
272 count = 0
273 start_pos = 1
274 do i = 1, len(path_env) + 1
275 if (i > len(path_env) .or. path_env(i:i) == path_sep) then
276 if (i > len(path_env)) then
277 end_pos = i - 1
278 else
279 end_pos = i - 1
280 end if
281
282 if (end_pos >= start_pos) then
283 count = count + 1
284 tmp(count) = trim(adjustl(path_env(start_pos:end_pos)))
285 end if
286 start_pos = i + 1
287 end if
288 end do
289
290 ! Allocate result array with actual count
291 if (count > 0) then
292 allocate(character(len=MAX_PATH_LEN) :: paths(count))
293 paths(:) = tmp(1:count)
294 else
295 allocate(character(len=0) :: paths(0))
296 end if
297 end function
298
299end module
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
Definition global.f90:96
recursive subroutine, public handle_include(ctx, ounit, preprocess, macros, token)
Process a include directive encountered during preprocessing Resolves the include file name (quoted o...
Definition include.f90:101
logical, public verbose
Master switch for verbose diagnostic output Default value is .false. (quiet mode)....
Definition logging.f90:108
pure logical function is_rooted(filepath)
Returns .true. if the path is rooted (starts with a separator) or is absolute. A rooted path begins w...
Definition path.f90:152
pure character(:) function, allocatable dirpath(filepath)
Returns the directory part of a path (everything before the last separator).
Definition path.f90:296
character(:) function, allocatable cwd()
Returns the current working directory as a deferred-length character string. Returns empty string on ...
Definition path.f90:394
pure character(len_trim(str)) function, public lowercase(str)
Convert string to lower case (respects contents of quotes).
Definition string.f90:642
Interface to render diagnostic messages and labels.
Definition logging.f90:185
Abstract interface for the main preprocessing routine (used for recursion) Allows handle_include to r...
Definition macro.f90:174
Generic interface for joining two path components Supports all combinations of character and string a...
Definition path.f90:102
Index operator.
Definition string.f90:180
Return the length of a string.
Definition string.f90:135
Return the trimmed string.
Definition string.f90:151
Source location and content snapshot for precise diagnostics Instances of this type are created for e...
Definition context.f90:99
Definition of diagnostic message.
Definition logging.f90:269
Represents text as a sequence of ASCII code units. The derived type wraps an allocatable character ar...
Definition logging.f90:246
Derived type representing a single preprocessor macro Extends string with macro-specific fields: rep...
Definition macro.f90:98