53 use,
intrinsic :: iso_fortran_env, only: stdout => output_unit, iostat_end, stdin => input_unit
54 use,
intrinsic :: iso_c_binding, only: c_char, c_size_t, c_ptr, c_null_ptr, c_associated, c_funloc
69 implicit none;
private
85 module procedure :: preprocess_file
86 module procedure :: preprocess_file_to_unit
87 module procedure :: preprocess_unit_to_file
88 module procedure :: preprocess_unit_to_unit
91 character(256) :: name
97 character(:),
allocatable :: res
98 character(:),
allocatable :: tmp
99 character(MAX_LINE_LEN) :: line
100 character(MAX_LINE_LEN) :: continued_line
102 integer :: icontinuation
114 subroutine preprocess_file(filepath, outputfile)
115 character(*),
intent(in) :: filepath
116 character(*),
intent(in),
optional :: outputfile
118 integer :: iunit, ierr, n, ounit
119 character(len=1, kind=c_char) :: buf(256)
121 open(newunit=iunit, file=filepath, status=
'old', action=
'read', iostat=ierr)
125 message=
'Error opening input file: ' //
trim(filepath), &
130 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
131 n = findloc(buf, achar(0), 1)
132 name = filepath(n + 1:)
136 if (
present(outputfile))
then
137 open(newunit=ounit, file=outputfile, status=
'replace', action=
'write', iostat=ierr)
140 message=
'Error opening input file: ' //
trim(outputfile), &
151 if (iunit /= stdin)
close(iunit)
152 if (ounit /= stdout)
close(ounit)
161 subroutine preprocess_unit_to_file(iunit, ofile)
162 integer,
intent(in) :: iunit
163 character(*),
intent(in) :: ofile
165 integer :: ierr, ounit
167 if (iunit /= stdin)
then
168 inquire(unit = iunit, name=name)
171 open(newunit=ounit, file=ofile, status=
'replace', action=
'write', iostat=ierr)
174 message=
'Error opening input file: ' //
trim(ofile), &
182 if (iunit /= stdin)
close(iunit)
183 if (ounit /= stdout)
close(ounit)
192 subroutine preprocess_file_to_unit(ifile, ounit)
193 character(*),
intent(in) :: ifile
194 integer,
intent(in) :: ounit
196 integer :: iunit, ierr, n
197 character(len=1, kind=c_char) :: buf(256)
199 open(newunit=iunit, file=ifile, status=
'old', action=
'read', iostat=ierr)
202 message=
'Error opening input file: ' //
trim(ifile), &
207 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
208 n = findloc(buf, achar(0), 1)
214 if (iunit /= stdin)
close(iunit)
215 if (ounit /= stdout)
close(ounit)
226 subroutine preprocess_unit_to_unit(iunit, ounit)
227 integer,
intent(in) :: iunit
228 integer,
intent(in) :: ounit
230 type(
macro),
allocatable :: macros(:)
232 if (.not.
allocated(
global%macros))
allocate(
global%macros(0))
234 if (.not.
allocated(
global%undef))
allocate(
global%undef(0))
235 if (.not.
allocated(
global%includedir))
allocate(
global%includedir(0))
241 reprocess = .false.; c_continue = .false.; f_continue = .false.
242 icontinuation = 1; iline = 0
243 continued_line =
''; res =
''
245 call preprocess_unit(iunit, ounit, macros, .false.)
263 subroutine preprocess_unit(iunit, ounit, macros, from_include)
264 integer,
intent(in) :: iunit
265 integer,
intent(in) :: ounit
266 type(
macro),
allocatable,
intent(inout) :: macros(:)
267 logical,
intent(in) :: from_include
272 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [in] '
273 read(iunit,
'(A)', iostat=ierr) line
275 if (
global%interactive)
then
280 if (ierr == iostat_end .and. from_include) f_continue =
tail(tmp) ==
'&'
283 if (.not. from_include) iline = iline + 1
286 continued_line = continued_line(:icontinuation) //
trim(adjustl(line))
288 continued_line =
trim(adjustl(line))
290 n =
len_trim(continued_line);
if (n == 0) cycle
293 if (verify(continued_line(n:n),
'\') == 0)
then
295 if (continued_line(
len_trim(continued_line) - 1:
len_trim(continued_line)) ==
'\\' .and.
global%line_break)
then
297 continued_line = continued_line(:
len_trim(continued_line) - 2) // new_line(
'A')
298 icontinuation =
len_trim(continued_line)
301 icontinuation =
len_trim(continued_line) - 1
302 continued_line = continued_line(:icontinuation)
308 tmp = process_line(continued_line, ounit, name, iline, macros, stitch)
311 in_comment =
head(tmp) ==
'!'
313 if (merge(
head(res) ==
'!', in_comment,
len_trim(res) > 0))
then
314 f_continue =
tail(tmp) ==
'&'
316 if (in_comment .and. f_continue) cycle
317 f_continue = .not. in_comment .and.
tail(tmp) ==
'&'
320 if ((.not.
global%disable_continuation) .and. (f_continue .or. stitch))
then
325 if (.not. in_comment .and.
head(res) ==
'!')
then
329 write(ounit,
'(A)') res
331 res = process_line(tmp, ounit, name, iline, macros, stitch)
333 res = process_line(
concat(res, tmp), ounit, name, iline, macros, stitch)
343 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [out] '
344 write(ounit,
'(A)') res
353 message=
'Unclosed conditional block at end of file', &
356 else if (c_continue)
then
358 message=
'Unexpected character', &
381 recursive function process_line(current_line, ounit, filepath, linenum, macros, stch)
result(rst)
382 character(*),
intent(in) :: current_line
383 integer,
intent(in) :: ounit
384 character(*),
intent(inout) :: filepath
385 integer,
intent(inout) :: linenum
386 type(
macro),
allocatable,
intent(inout) :: macros(:)
387 logical,
intent(out) :: stch
388 character(:),
allocatable :: rst
390 character(:),
allocatable :: trimmed_line
392 logical,
save :: l_in_comment = .false., l_in_loop = .false.
393 integer :: idx, comment_start, comment_end, n
396 trimmed_line =
trim(adjustl(current_line))
398 comment_end =
index(trimmed_line,
'*/')
399 if (l_in_comment .and. comment_end > 0)
then
400 trimmed_line = trimmed_line(comment_end + 2:)
401 l_in_comment = .false.
404 if (l_in_comment)
return
405 comment_start =
index(trimmed_line,
'/*')
406 if (comment_start > 0)
then
407 trimmed_line = trimmed_line(:comment_start - 1)
408 l_in_comment = comment_end == 0
410 n =
len(trimmed_line);
if (n == 0)
return
413 ctx =
context(trimmed_line, linenum, filepath)
414 if (
head(trimmed_line) ==
'#')
then
415 if (
len(trimmed_line) == 1)
then
422 if (
global%support_forloop)
call handle_endfor(ctx, ounit, c_funloc(process_line), macros,
'endfor')
424 else if (l_in_loop)
then
435 call handle_include(ctx, ounit, preprocess_unit, macros,
'include')
459 else if (active)
then
464 global%implicit_continuation))
subroutine, public handle_ifndef(ctx, macros, token)
Process ifndef – test if a macro is NOT defined.
logical function, public is_active()
Determine if current line is inside an active conditional block.
subroutine, public handle_ifdef(ctx, macros, token)
Process ifdef – test if a macro is defined.
integer, public cond_depth
Current nesting depth of conditional directives (0 = outside any if).
subroutine, public handle_elif(ctx, macros, token)
Process elif – alternative branch after if/elif Only activates if no previous branch in the group was...
subroutine, public handle_elifndef(ctx, macros, token)
Process elifndef – test if a macro is not defined.
subroutine, public handle_else(ctx)
Process else – final fallback branch Activates only if no previous if/elif branch was true.
type(cond_state), dimension(max_cond_depth), public cond_stack
Global stack of conditional states (depth-limited).
subroutine, public handle_if(ctx, macros, token)
Process a if directive with constant expression evaluation Evaluates the expression after if using ev...
subroutine, public handle_elifdef(ctx, macros, token)
Process elifdef – test if a macro is defined.
subroutine, public handle_endif(ctx)
Process endif – end of conditional block Pops the top state from the stack. Reports error on unmatche...
subroutine, public handle_undef(ctx, macros, token)
Process a undef directive and remove a macro from the table Finds the named macro in the current tabl...
subroutine, public handle_define(ctx, macros, token)
Process a define directive and register or update a macro Parses the line after #define,...
subroutine, public handle_error(ctx, macros, token)
Process a error directive. It causes the preprocessor to report a fatal error that stops the preproce...
subroutine, public handle_warning(ctx, macros, token)
Process a warning directive. It causes the preprocessor to report a warning that does not stop the pr...
subroutine, public handle_for(ctx, macros, token)
Process a #for directive and initialize a new loop context.
subroutine, public add_to_loop(line)
Append a source line to the currently active loop body.
subroutine, public handle_endfor(ctx, ounit, p, macros, token)
Finalize a loop and emit all expanded iterations.
logical function, public is_in_forloop()
Query whether parsing is currently inside a #for block.
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
recursive subroutine, public handle_include(ctx, ounit, preprocess, macros, token)
Process a include directive encountered during preprocessing Resolves the include file name (quoted o...
subroutine, public handle_line(ctx, token)
Handle the standard line directive Supports two standard forms: line <number> line <number> "<filenam...
character(:) function, allocatable, public expand_all(ctx, macros, stitch, has_extra, implicit_conti, dollar_insert)
Fully expand a line including predefined macros (FILE, LINE, etc.) First performs normal macro expans...
character function, public tail(str)
Returns the last non-blank character of a string.
pure character(len_trim(str)) function, public lowercase(str)
Convert string to lower case (respects contents of quotes).
character(:) function, allocatable, public concat(str1, str2)
Smart concatenation that removes continuation markers (&) and handles line-continuation rules.
logical function, public starts_with(str, arg1, idx)
Checks if a string starts with a given prefix Returns .true. if the string str (after trimming leadin...
character function, public head(str)
Returns the first non-blank character of a string.
Interface to render diagnostic messages and labels.
Return current number of stored macros.
Generic interface to start preprocessing from various sources/sinks.
Return the trimmed length of a string.
Return the length of a string.
Return the trimmed string.
Source location and content snapshot for precise diagnostics Instances of this type are created for e...
Definition of diagnostic message.
Represents text as a sequence of ASCII code units. The derived type wraps an allocatable character ar...
Derived type representing a single preprocessor macro Extends string with macro-specific fields: rep...