93 use,
intrinsic :: iso_fortran_env, only: stdout => output_unit, iostat_end, stdin => input_unit
94 use,
intrinsic :: iso_c_binding, only: c_char, c_size_t, c_ptr, c_null_ptr, c_associated, c_funloc
109 implicit none;
private
138 module procedure :: preprocess_file
139 module procedure :: preprocess_file_to_unit
140 module procedure :: preprocess_unit_to_file
141 module procedure :: preprocess_unit_to_unit
144 character(256) :: name
145 logical :: c_continue
146 logical :: f_continue
147 logical :: in_comment
150 character(:),
allocatable :: res
151 character(:),
allocatable :: tmp
152 character(MAX_LINE_LEN) :: line
153 character(MAX_LINE_LEN) :: continued_line
155 integer :: icontinuation
166 subroutine preprocess_file(filepath, outputfile)
167 character(*),
intent(in) :: filepath
168 character(*),
intent(in),
optional :: outputfile
170 integer :: iunit, ierr, n, ounit
171 character(len=1, kind=c_char) :: buf(256)
173 open(newunit=iunit, file=filepath, status=
'old', action=
'read', iostat=ierr)
177 message=
'Error opening input file: ' //
trim(filepath), &
182 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
183 n = findloc(buf, achar(0), 1)
184 name = filepath(n + 1:)
188 if (
present(outputfile))
then
189 open(newunit=ounit, file=outputfile, status=
'replace', action=
'write', iostat=ierr)
192 message=
'Error opening input file: ' //
trim(outputfile), &
203 if (iunit /= stdin)
close(iunit)
204 if (ounit /= stdout)
close(ounit)
212 subroutine preprocess_unit_to_file(iunit, ofile)
213 integer,
intent(in) :: iunit
214 character(*),
intent(in) :: ofile
216 integer :: ierr, ounit
218 if (iunit /= stdin)
then
219 inquire(unit = iunit, name=name)
222 open(newunit=ounit, file=ofile, status=
'replace', action=
'write', iostat=ierr)
225 message=
'Error opening input file: ' //
trim(ofile), &
233 if (iunit /= stdin)
close(iunit)
234 if (ounit /= stdout)
close(ounit)
242 subroutine preprocess_file_to_unit(ifile, ounit)
243 character(*),
intent(in) :: ifile
244 integer,
intent(in) :: ounit
246 integer :: iunit, ierr, n
247 character(len=1, kind=c_char) :: buf(256)
249 open(newunit=iunit, file=ifile, status=
'old', action=
'read', iostat=ierr)
252 message=
'Error opening input file: ' //
trim(ifile), &
257 if (c_associated(getcwd_c(buf,
size(buf, kind=c_size_t))))
then
258 n = findloc(buf, achar(0), 1)
264 if (iunit /= stdin)
close(iunit)
265 if (ounit /= stdout)
close(ounit)
280 subroutine preprocess_unit_to_unit(iunit, ounit)
281 integer,
intent(in) :: iunit
282 integer,
intent(in) :: ounit
284 type(
macro),
allocatable :: macros(:)
286 if (.not.
allocated(
global%macros))
allocate(
global%macros(0))
288 if (.not.
allocated(
global%undef))
allocate(
global%undef(0))
289 if (.not.
allocated(
global%includedir))
allocate(
global%includedir(0))
295 reprocess = .false.; c_continue = .false.; f_continue = .false.
296 icontinuation = 1; iline = 0
297 continued_line =
''; res =
''
299 call preprocess_unit(iunit, ounit, macros, .false.)
319 subroutine preprocess_unit(iunit, ounit, macros, from_include)
320 integer,
intent(in) :: iunit
321 integer,
intent(in) :: ounit
322 type(
macro),
allocatable,
intent(inout) :: macros(:)
323 logical,
intent(in) :: from_include
328 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [in] '
329 read(iunit,
'(A)', iostat=ierr) line
331 if (
global%interactive)
then
336 if (ierr == iostat_end .and. from_include) f_continue =
tail(tmp) ==
'&'
339 if (.not. from_include) iline = iline + 1
342 continued_line = continued_line(:icontinuation) //
trim(adjustl(line))
344 continued_line =
trim(adjustl(line))
346 n =
len_trim(continued_line);
if (n == 0) cycle
349 if (verify(continued_line(n:n),
'\') == 0)
then
351 if (continued_line(
len_trim(continued_line) - 1:
len_trim(continued_line)) ==
'\\' .and.
global%line_break)
then
353 continued_line = continued_line(:
len_trim(continued_line) - 2) // new_line(
'A')
354 icontinuation =
len_trim(continued_line)
357 icontinuation =
len_trim(continued_line) - 1
358 continued_line = continued_line(:icontinuation)
364 tmp = process_line(continued_line, ounit, name, iline, macros, stitch)
367 in_comment =
head(tmp) ==
'!'
369 if (merge(
head(res) ==
'!', in_comment,
len_trim(res) > 0))
then
370 f_continue =
tail(tmp) ==
'&'
372 if (in_comment .and. f_continue) cycle
373 f_continue = .not. in_comment .and.
tail(tmp) ==
'&'
376 if ((.not.
global%disable_continuation) .and. (f_continue .or. stitch))
then
381 if (.not. in_comment .and.
head(res) ==
'!')
then
385 write(ounit,
'(A)') res
387 res = process_line(tmp, ounit, name, iline, macros, stitch)
389 res = process_line(
concat(res, tmp), ounit, name, iline, macros, stitch)
399 if (
global%interactive)
write(*,
'(/a)', advance=
'no')
' [out] '
400 write(ounit,
'(A)') res
409 message=
'Unclosed conditional block at end of file', &
412 else if (c_continue)
then
414 message=
'Unexpected character', &
447 recursive function process_line(current_line, ounit, filepath, linenum, macros, stch)
result(rst)
448 character(*),
intent(in) :: current_line
449 integer,
intent(in) :: ounit
450 character(*),
intent(inout) :: filepath
451 integer,
intent(inout) :: linenum
452 type(
macro),
allocatable,
intent(inout) :: macros(:)
453 logical,
intent(out) :: stch
454 character(:),
allocatable :: rst
456 character(:),
allocatable :: trimmed_line
458 logical,
save :: l_in_comment = .false., l_in_loop = .false.
459 integer :: idx, comment_start, comment_end, n
462 trimmed_line =
trim(adjustl(current_line))
464 comment_end =
index(trimmed_line,
'*/')
465 if (l_in_comment .and. comment_end > 0)
then
466 trimmed_line = trimmed_line(comment_end + 2:)
467 l_in_comment = .false.
470 if (l_in_comment)
return
471 comment_start =
index(trimmed_line,
'/*')
472 if (comment_start > 0)
then
473 trimmed_line = trimmed_line(:comment_start - 1)
474 l_in_comment = comment_end == 0
476 n =
len(trimmed_line);
if (n == 0)
return
479 ctx =
context(trimmed_line, linenum, filepath)
480 if (
head(trimmed_line) ==
'#')
then
481 if (
len(trimmed_line) == 1)
then
488 if (
global%support_forloop)
call handle_endfor(ctx, ounit, c_funloc(process_line), macros,
'endfor')
490 else if (l_in_loop)
then
501 call handle_include(ctx, ounit, preprocess_unit, macros,
'include')
525 else if (active)
then
530 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 whether the current source position is active.
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.
subroutine, public handle_define(ctx, macros, token)
Process a #define directive.
subroutine, public handle_error(ctx, macros, token)
Process a #error directive.
subroutine, public handle_warning(ctx, macros, token)
Process a #warning directive.
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 innermost 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. This routine is typically used by the main pr...
type(global_settings), public global
Global preprocessor configuration instance.
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)
Process a #line directive.
character(:) function, allocatable, public expand_all(ctx, macros, stitch, has_extra, implicit_conti, dollar_insert)
Expand a source line including predefined macros.
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 character of the trimmed string.
Generic renderer for diagnostics and source excerpts.
Return the number of stored macro definitions.
Generic interface to start preprocessing from various sources/sinks.
Locate the position of a substring.
Return the trimmed length of a string object.
Return the length of a string object.
Remove trailing blanks from a string object.
Snapshot of a source location within the preprocessing stream.
Structured compiler diagnostic.
Diagnostic label identifying a region of source text.
Representation of a preprocessor macro.