51 use,
intrinsic :: iso_fortran_env, only: stdout => output_unit, iostat_end, stdin => input_unit
52 use,
intrinsic :: iso_c_binding, only: c_char, c_size_t, c_ptr, c_null_ptr, c_associated
65 implicit none;
private
87 character(256) :: name
93 character(:),
allocatable :: res
94 character(:),
allocatable :: tmp
95 character(MAX_LINE_LEN) :: line
96 character(MAX_LINE_LEN) :: continued_line
98 integer :: icontinuation
111 character(*),
intent(in) :: filepath
112 character(*),
intent(in),
optional :: outputfile
114 integer :: iunit, ierr, n, ounit
115 character(len=1, kind=c_char) :: buf(256)
117 open(newunit=iunit, file=filepath, status=
'old', action=
'read', iostat=ierr)
120 message=
'Error opening input file: ' //
trim(filepath), &
125 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
126 n = findloc(buf, achar(0), 1)
127 name = filepath(n + 1:)
131 if (
present(outputfile))
then
132 open(newunit=ounit, file=outputfile, status=
'replace', action=
'write', iostat=ierr)
135 message=
'Error opening input file: ' //
trim(outputfile), &
146 if (iunit /= stdin)
close(iunit)
147 if (ounit /= stdout)
close(ounit)
157 integer,
intent(in) :: iunit
158 character(*),
intent(in) :: ofile
160 integer :: ierr, ounit
162 if (iunit /= stdin)
then
163 inquire(unit = iunit, name=name)
166 open(newunit=ounit, file=ofile, status=
'replace', action=
'write', iostat=ierr)
169 message=
'Error opening input file: ' //
trim(ofile), &
177 if (iunit /= stdin)
close(iunit)
178 if (ounit /= stdout)
close(ounit)
188 character(*),
intent(in) :: ifile
189 integer,
intent(in) :: ounit
191 integer :: iunit, ierr, n
192 character(len=1, kind=c_char) :: buf(256)
194 open(newunit=iunit, file=ifile, status=
'old', action=
'read', iostat=ierr)
197 message=
'Error opening input file: ' //
trim(ifile), &
202 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
203 n = findloc(buf, achar(0), 1)
209 if (iunit /= stdin)
close(iunit)
210 if (ounit /= stdout)
close(ounit)
222 integer,
intent(in) :: iunit
223 integer,
intent(in) :: ounit
225 type(
macro),
allocatable :: macros(:)
227 if (.not.
allocated(
global%macros))
allocate(
global%macros(0))
229 if (.not.
allocated(
global%undef))
allocate(
global%undef(0))
230 if (.not.
allocated(
global%includedir))
allocate(
global%includedir(0))
236 reprocess = .false.; c_continue = .false.; f_continue = .false.
237 icontinuation = 1; iline = 0
238 continued_line =
''; res =
''
259 integer,
intent(in) :: iunit
260 integer,
intent(in) :: ounit
261 type(
macro),
allocatable,
intent(inout) :: macros(:)
262 logical,
intent(in) :: from_include
267 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [in] '
268 read(iunit,
'(A)', iostat=ierr) line
270 if (
global%interactive)
then
275 if (ierr == iostat_end .and. from_include) f_continue =
tail(tmp) ==
'&'
278 if (.not. from_include) iline = iline + 1
281 continued_line = continued_line(:icontinuation) //
trim(adjustl(line))
283 continued_line =
trim(adjustl(line))
285 n =
len_trim(continued_line);
if (n == 0) cycle
288 if (verify(continued_line(n:n),
'\') == 0)
then
290 if (continued_line(
len_trim(continued_line) - 1:
len_trim(continued_line)) ==
'\\' .and.
global%line_break)
then
292 continued_line = continued_line(:
len_trim(continued_line) - 2) // new_line(
'A')
293 icontinuation =
len_trim(continued_line)
296 icontinuation =
len_trim(continued_line) - 1
297 continued_line = continued_line(:icontinuation)
303 tmp =
process_line(continued_line, ounit, name, iline, macros, stitch)
306 in_comment =
head(tmp) ==
'!'
308 if (merge(
head(res) ==
'!', in_comment,
len_trim(res) > 0))
then
309 f_continue =
tail(tmp) ==
'&'
311 if (in_comment .and. f_continue) cycle
312 f_continue = .not. in_comment .and.
tail(tmp) ==
'&'
315 if (f_continue .or. stitch)
then
320 if (.not. in_comment .and.
head(res) ==
'!')
then
321 write(ounit,
'(A)') res
322 res =
process_line(tmp, ounit, name, iline, macros, stitch)
330 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [out] '
331 write(ounit,
'(A)') res
339 message=
'Unclosed conditional block at end of file', &
340 label=
label_type(
'Missing conditional statement #endif', 1, 1), &
343 else if (c_continue)
then
345 message=
'Unexpected character', &
368 recursive function process_line(current_line, ounit, filepath, linenum, macros, stch)
result(rst)
369 character(*),
intent(in) :: current_line
370 integer,
intent(in) :: ounit
371 character(*),
intent(inout) :: filepath
372 integer,
intent(inout) :: linenum
373 type(
macro),
allocatable,
intent(inout) :: macros(:)
374 logical,
intent(out) :: stch
375 character(:),
allocatable :: rst
377 character(:),
allocatable :: trimmed_line
379 logical,
save :: l_in_comment = .false.
380 integer :: idx, comment_start, comment_end, n
383 trimmed_line =
trim(adjustl(current_line))
385 comment_end =
index(trimmed_line,
'*/')
386 if (l_in_comment .and. comment_end > 0)
then
387 trimmed_line = trimmed_line(comment_end + 2:)
388 l_in_comment = .false.
391 if (l_in_comment)
return
392 comment_start =
index(trimmed_line,
'/*')
393 if (comment_start > 0)
then
394 trimmed_line = trimmed_line(:comment_start - 1)
395 l_in_comment = comment_end == 0
397 n =
len(trimmed_line);
if (n == 0)
return
400 ctx =
context(trimmed_line, linenum, filepath)
401 if (
head(trimmed_line) ==
'#')
then
402 if (
len(trimmed_line) == 1)
then
415 call handle_line(ctx,
'line')
437 else if (active)
then
438 if (.not.
global%expand_macros)
then
442 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...
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...
character(:) function, allocatable, public expand_all(ctx, macros, stitch, has_extra, implicit_conti)
Fully expand a line including predefined macros (FILE, LINE, etc.) First performs normal macro expans...
subroutine preprocess_file_to_unit(ifile, ounit)
Preprocess a file and write to an already-open output unit.
subroutine preprocess_unit_to_unit(iunit, ounit)
Core preprocessing routine: read from iunit, write to ounit Sets up a clean macro environment for the...
subroutine preprocess_unit(iunit, ounit, macros, from_include)
Worker routine that reads lines, handles continuations, comments and directives This is the main loop...
subroutine preprocess_file(filepath, outputfile)
Preprocess a file and write result to an optional output file (default: stdout) Opens the input file,...
subroutine preprocess_unit_to_file(iunit, ofile)
Preprocess from an already-open input unit and write to a file.
recursive character(:) function, allocatable process_line(current_line, ounit, filepath, linenum, macros, stch)
Process a single (possibly continued) line – handles directives and macro expansion Responsibilities:
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...