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
66 implicit none;
private
88 character(256) :: name
94 character(:),
allocatable :: res
95 character(:),
allocatable :: tmp
96 character(MAX_LINE_LEN) :: line
97 character(MAX_LINE_LEN) :: continued_line
99 integer :: icontinuation
112 character(*),
intent(in) :: filepath
113 character(*),
intent(in),
optional :: outputfile
115 integer :: iunit, ierr, n, ounit
116 character(len=1, kind=c_char) :: buf(256)
118 open(newunit=iunit, file=filepath, status=
'old', action=
'read', iostat=ierr)
121 message=
'Error opening input file: ' //
trim(filepath), &
126 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
127 n = findloc(buf, achar(0), 1)
128 name = filepath(n + 1:)
132 if (
present(outputfile))
then
133 open(newunit=ounit, file=outputfile, status=
'replace', action=
'write', iostat=ierr)
136 message=
'Error opening input file: ' //
trim(outputfile), &
147 if (iunit /= stdin)
close(iunit)
148 if (ounit /= stdout)
close(ounit)
158 integer,
intent(in) :: iunit
159 character(*),
intent(in) :: ofile
161 integer :: ierr, ounit
163 if (iunit /= stdin)
then
164 inquire(unit = iunit, name=name)
167 open(newunit=ounit, file=ofile, status=
'replace', action=
'write', iostat=ierr)
170 message=
'Error opening input file: ' //
trim(ofile), &
178 if (iunit /= stdin)
close(iunit)
179 if (ounit /= stdout)
close(ounit)
189 character(*),
intent(in) :: ifile
190 integer,
intent(in) :: ounit
192 integer :: iunit, ierr, n
193 character(len=1, kind=c_char) :: buf(256)
195 open(newunit=iunit, file=ifile, status=
'old', action=
'read', iostat=ierr)
198 message=
'Error opening input file: ' //
trim(ifile), &
203 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
204 n = findloc(buf, achar(0), 1)
210 if (iunit /= stdin)
close(iunit)
211 if (ounit /= stdout)
close(ounit)
223 integer,
intent(in) :: iunit
224 integer,
intent(in) :: ounit
226 type(
macro),
allocatable :: macros(:)
228 if (.not.
allocated(
global%macros))
allocate(
global%macros(0))
230 if (.not.
allocated(
global%undef))
allocate(
global%undef(0))
231 if (.not.
allocated(
global%includedir))
allocate(
global%includedir(0))
237 reprocess = .false.; c_continue = .false.; f_continue = .false.
238 icontinuation = 1; iline = 0
239 continued_line =
''; res =
''
260 integer,
intent(in) :: iunit
261 integer,
intent(in) :: ounit
262 type(
macro),
allocatable,
intent(inout) :: macros(:)
263 logical,
intent(in) :: from_include
268 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [in] '
269 read(iunit,
'(A)', iostat=ierr) line
271 if (
global%interactive)
then
276 if (ierr == iostat_end .and. from_include) f_continue =
tail(tmp) ==
'&'
279 if (.not. from_include) iline = iline + 1
282 continued_line = continued_line(:icontinuation) //
trim(adjustl(line))
284 continued_line =
trim(adjustl(line))
286 n =
len_trim(continued_line);
if (n == 0) cycle
289 if (verify(continued_line(n:n),
'\') == 0)
then
291 if (continued_line(
len_trim(continued_line) - 1:
len_trim(continued_line)) ==
'\\' .and.
global%line_break)
then
293 continued_line = continued_line(:
len_trim(continued_line) - 2) // new_line(
'A')
294 icontinuation =
len_trim(continued_line)
297 icontinuation =
len_trim(continued_line) - 1
298 continued_line = continued_line(:icontinuation)
304 tmp =
process_line(continued_line, ounit, name, iline, macros, stitch)
307 in_comment =
head(tmp) ==
'!'
309 if (merge(
head(res) ==
'!', in_comment,
len_trim(res) > 0))
then
310 f_continue =
tail(tmp) ==
'&'
312 if (in_comment .and. f_continue) cycle
313 f_continue = .not. in_comment .and.
tail(tmp) ==
'&'
316 if (f_continue .or. stitch)
then
321 if (.not. in_comment .and.
head(res) ==
'!')
then
322 write(ounit,
'(A)') res
323 res =
process_line(tmp, ounit, name, iline, macros, stitch)
331 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [out] '
332 write(ounit,
'(A)') res
340 message=
'Unclosed conditional block at end of file', &
341 label=
label_type(
'Missing conditional statement #endif', 1, 1), &
344 else if (c_continue)
then
346 message=
'Unexpected character', &
369 recursive function process_line(current_line, ounit, filepath, linenum, macros, stch)
result(rst)
370 character(*),
intent(in) :: current_line
371 integer,
intent(in) :: ounit
372 character(*),
intent(inout) :: filepath
373 integer,
intent(inout) :: linenum
374 type(
macro),
allocatable,
intent(inout) :: macros(:)
375 logical,
intent(out) :: stch
376 character(:),
allocatable :: rst
378 character(:),
allocatable :: trimmed_line
380 logical,
save :: l_in_comment = .false.
381 integer :: idx, comment_start, comment_end, n
384 trimmed_line =
trim(adjustl(current_line))
386 comment_end =
index(trimmed_line,
'*/')
387 if (l_in_comment .and. comment_end > 0)
then
388 trimmed_line = trimmed_line(comment_end + 2:)
389 l_in_comment = .false.
392 if (l_in_comment)
return
393 comment_start =
index(trimmed_line,
'/*')
394 if (comment_start > 0)
then
395 trimmed_line = trimmed_line(:comment_start - 1)
396 l_in_comment = comment_end == 0
398 n =
len(trimmed_line);
if (n == 0)
return
401 ctx =
context(trimmed_line, linenum, filepath)
402 if (
head(trimmed_line) ==
'#')
then
403 if (
len(trimmed_line) == 1)
then
438 else if (active)
then
439 if (.not.
global%expand_macros)
then
443 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...
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)
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...