Small Fortran tools

I wrote my own subset of the PXF library for personal use - almost all in C. I wrote test programs for all the calls, and found some bugs in the Cray implementation. Since I worked for Cray->SGI at the time, I was able to get them fixed. Later, when Intel started supporting it, I used my tests and found some bugs in their implementation - now long since fixed.

A big problem with PXF was that the spec was written with F77 in mind. This created obvious problems when data structures were needed. People didn’t like using PXFSTRUCTCREATE and associated handles. The other problem was the old ‘default integer’ issue on 32-bit systems with file and memory sizes and the like. Finally, for Fortran 90 users, a module should have been required. (My library has a ‘pxf_definitions’ module.)

I started re-writing my library a couple of years ago replacing as much C as I could with Fortran, but it remains an unfinished work. Either way, a few of the I/O calls can’t be implemented unless one has access to the compilers libraries - PXFFDOPEN is one. Eventually I’ll get it all working with gfortran.

Wholeheartedly agree that toupper and tolower should be Standard intrinsic functions. Same with sort. Seems like almost any significant Fortran program uses homegrown versions. I’ve suggested sort, along with grade, to several Committee members over the years to no avail. HPF had specs for sort_up/sort_down and grade_up/grade_down functions that could be used as a basis. But something simpler (e.g., without multiple dimensions and the DIM= complications) would be fine by me.

Lots of nice things in your GPF repository!

1 Like

Not saying this will change minds in the to_upper/to_lower debate, but it’s definitely worth a watch and thinking about.

4 Likes

Looking at how other languages did it: Go used to have strings.Title, strings.ToUpper and strings.ToLower, until it introduced a cases package that allows you to select the case you want but for a particular language (with English as the fallback language).

(The strings.* functions are still available, since Go never removes anything but instead the tooling around the language forces you to move away from deprecated stuff)

Given the fact that the Fortran Standards tend to favor simplicity over quality of life, this approach is probably deemed too complicated —but it’s certainly doable with the aid of an ISO_FORTRAN_CHARS intrinsic module or something like that.

My own personal opinion is that the last few iterations of the Standard (and the Standards Committee) have been more focused on people writing libraries instead of standalone applications. I see your “simplicity over quality of life” as an example of this focus which is the only way I can explain some of the additions to the language that I don’t see anyone writing a standalone scientific application would ever use (but might find a use for when writing a library).

It is a fun presentation. He didn’t include BAUDOT, the original 5-level Teletypes, BCD, EBCDIC, etc. The computer world was pretty much stuck with 6-bit character sets until the mid-1960s. Even in Fortran-77, when the character data type was specified, 6-bit character sets were still common - especially in the 60-bit CDC world. (The 36-bit Univac/Unisys systems too - though their Fortran 77 compiler was called ASCII Fortran and used nine bits per character. Their older compilers used 6-bit FIELDATA.)

But these days Fortran now has a precedent of ASCII-like characters for default character kind. (See section 7.4.4.1 in F2023.) Things like the ACHAR, IACHAR, and some comparison intrinsics are also defined in terms of ASCII. So for default character kind, defining to_lower and to_upper in terms of the ASCII subset seems like a no-brainer. If a specific implementation were to offer a UTF-8 or some other character kind, it might or might not be supported.

As to grading/sorting, the HPF spec also specified ASCII-like collating sequence. And see 7.4.4.4 of F2023.

More a tip than a tool, but backtabbing is sometimes overlooked in the strange little world of Fortran descriptors …

I just had someone mention to me they did not know you could tab back left and write on occupied character positions. That can be handy. For instance …

program testit
implicit none
character(len=*),parameter :: it='(*(1x,g0))'
character(len=*),parameter :: break='(/,80("-"),t4,a)'
character(len=*),parameter :: pagen='(/,4x,50("."),t4,a,t56,g0)'
   print break, ' Section I '  
   print pagen, ' scalars ' , 'pg 1' 
   print pagen, ' vectors ' , 'pg 3' 
   print pagen, ' matrices ' , 'pg 8' 
   print pagen, ' arrays ' , 'pg 15' 
   print pagen, ' non-standard bounds ' , 'pg 20' 
   print break, ' Section II '  
end program testit
--- Section I ------------------------------------------------------------------

    scalars .......................................... pg 1

    vectors .......................................... pg 3

    matrices ......................................... pg 8

    arrays ........................................... pg 15

    non-standard bounds .............................. pg 20

--- Section II -----------------------------------------------------------------
3 Likes

Maintaining recent versions of multiple Fortran compilers can be
cumbersome if you infrequently need some of the compilers, so something
like the Compiler Explorer webpage is handly for running short standalone
minimal reproducers on a variety of compilers.

If you are not a GUI user the command line interface provided at
GitHub - xfgusta/cexpl: Command-line tool to interact with Compiler Explorer · GitHub lets you easily compiler and run these
little cases (Something that uses only a few seconds compile and execution
time essentially what is suitable).

If you want to run on multiple compilers and use custom options even the
simple-to-use cexpl interface can benefit from a wrapper.

Here is a bash shell that can act as a model for such a wrapper. You almost
certainly will want to customize the compiler options and the list of
compilers but with cexpl(1) installed you can run simple codes on half a
dozen common Fortran compilers without requiring any of the to be installed
and compare ouput with a single command line.

The output from the following script is formatted for use with Discourse.
So running

fall -e hello.f90

Will generate markdown output that shows the input code and the results
from a variety of compilers with the output prefixed with a line count and
the name of the Compiler Explorer mnemonic for the compiler. The example
output follows the script.

Example bash script

##!/bin/bash
#@(#) fall(1) - call Compiler Explorer Fortran compilers for specified file
################################################################################
HELP(){
cat <<\EOF
NAME
   fall(1) - run a simple self-contained program with a variety of
   Fortran compilers using cexpl(1).
SYNOPSIS
command format:

    fall Fortran_Source_File
      [-e]                       Execute program
      [--cflags FLAGS]           Additional compiler flags
      [--args "ARGV [ARGV ..."]  Command line arguments to pass
      [--stdin STDIN ]           Data to read as stdin
DESCRIPTION

   fall(1) is a wrapper around cexpl(1). cexpl(1) is a command-line tool
   to interact with Compiler Explorer.

   Typically you only get a few seconds for a compilation and a few
   seconds for an execution.

   Note that in CE #include directives can access files via curl(1).

   The environment variable COMPILER may be set to a list of
   compilers. The default is

       aoccflang520        -  AOCC Flang 5.2.0
       flangtrunk          -  flang-trunk
       gfortran161         -  x86-64 gfortran 16.1
       ifxlatest           -  x86-64 ifx (latest)
       lfortran0590        -  LFortran 0.59.0
       nvfortran_x86_26_3  -  x86 nvfortran 26.3

OPTIONS:
   optional arguments to pass to cexpl:

     -e                                         # execute
     --cflags FLAGS                             # compiler flags
     -a ARGV [ARGV ...], --args ARGV [ARGV ...] # the command-line arguments
     --stdin STDIN [STDIN ...]                  # file to read as STDIN

EXAMPLE:

   fall hello.f90 |tee log.txt
   # execute as well as compile
   fall hello.f90 -e |tee log.txt
   # execute reading file "in" from stdin
   fall hello.f90 --stdin="`cat in`" -e |tee log.txt

SEE ALSO:
   cexpl --list-langs
   cexpl --list-compilers fortran
   # display predefined macros
   cexpl --compiler aoccflang520 --cflags='-E -dM -Mpreprocess my_file.f90'
EOF
}
################################################################################
if [ "$#" -eq 0 ]
then
   HELP
   exit
fi
################################################################################
# main
FIRSTWORD=${1:-''}
case "$FIRSTWORD" in
--help|-help|-h)
   HELP=TRUE
;;
*)
   echo '### Fortran source'
   echo '[details="click to see more..."]'
   echo '```fortran'
   for ARG in "$@"
   do
      case "$ARG" in 
      *90|*.f|*.F|*.ftn|*.FTN)
         [ -e "$ARG" ] && cat $ARG
      ;;
      esac
   done
   echo '```'
   echo '[/details]'
;;
esac
#------------------------------------------------------------------------------#
(exec 2>&1
ALL='aoccflang520 flangtrunk ifxlatest lfortran0590 nvfortran_x86_26_3 gfortran161'
for COMPILER in ${COMPILER:-$ALL}
do
   PREFIX=$(printf '%-20s :' $COMPILER)
   if [ "$HELP" = TRUE ]
   then
     (set -v -x
     TMP=/tmp/scr_$(uuidgen)
     touch $TMP
     cexpl -e -c $COMPILER --skip-asm --cflags='--help' $TMP 2>&1
     rm -f $TMP
     ) | sed -e "s/^/$PREFIX/"
   else
     echo "### compiler $COMPILER"
     echo '[details="click to see output..."]'
     echo '```text'
     case "$COMPILER" in
     lfortran*)     OPTIONS='--no-style-suggestions --cpp -DCOMPILER_='"$COMPILER" ;;
     ifx*)          OPTIONS='-fpp -warn all -check all,nouninit -error-limit 1 -O0 -g -assume byterecl -traceback -DCOMPILER_='"$COMPILER" ;;
     flangtrunc*)   OPTIONS='-cpp -DCOMPILER_='"$COMPILER" ;;
     aoccflang520)  OPTIONS='-cpp -DCOMPILER_='"$COMPILER" ;;
     nvfortran*)    OPTIONS='-Mbackslash -Mpreprocess -DCOMPILER_='"$COMPILER" ;;
     gfortran*)     OPTIONS='-cpp -Wall -Wextra -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -DCOMPILER_='"$COMPILER" ;;
     fppc64g9)      OPTIONS='-cpp -DCOMPILER_='"$COMPILER" ;;
     fppc64leg9)    OPTIONS='-cpp -DCOMPILER_='"$COMPILER" ;;
     *)             OPTIONS='' ;;
     esac
     (set -v -x
     cexpl -c $COMPILER --skip-asm --cflags="$OPTIONS" "$@" 2>&1
     ) | cat -nev | sed -e "s/^/$PREFIX/"
     echo '```'
     echo '[/details]'
   fi
   echo
done)
################################################################################
exit
################################################################################

Output Example

This example program echoes out the options it was compiled with and output
from inquiries of the timing routines available as an example:

Fortran source

click to see more...
program test_id
use, intrinsic                 :: iso_fortran_env, only : int32, int64
implicit none
   call platform()
   write(*,*)
   call clockrate()
   write(*,*)
   call machineid()
contains

subroutine clockrate()
integer(kind=int64)            :: count64, count_rate64, count_max64
integer(kind=int64)            :: count64b
character(len=*), parameter    :: g = '(1x,*(g0,1x))'
integer(kind=int64)            :: i
integer(kind=int64)            :: j
   print g, huge(0_int64)
   call system_clock(count64,count_rate64,count_max64)
   print g,  'SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type):'
   print g, 'COUNT_MAX(64bit)=    ', three(count_max64)
   print g, 'COUNT_RATE(64bit)=   ', three(count_rate64)
   print g, 'CURRENT COUNT(64bit)=', three(count64)

   do j=1,10
      call system_clock(count64,count_rate64,count_max64)
      do i=1,huge(0_int64)-1
         call system_clock(count64b,count_rate64,count_max64)
         if(count64b.ne.count64)then
            write(*,'(1x,g0)',advance='no')count64b-count64
            exit
         endif
      enddo
   enddo
   write(*,*)
end subroutine clockrate

subroutine platform()
use, intrinsic                 :: iso_fortran_env, only : compiler_version
use, intrinsic                 :: iso_fortran_env, only : compiler_options
implicit none
character(len=:),allocatable   :: version, options
character(len=*),parameter     :: nl=new_line('a')
integer                        :: where, start, break, i, last, col
   version=compiler_version()//' '
   options=' '//compiler_options()
   start=1
   do
      where=index(options(start:),' -')
      if(where.eq.0)exit
      break=where+start-1
      options(break:break)=nl
      start=where
   enddo
   if(start.eq.1)then
      do
         where=index(options(start:),' /')
         if(where.eq.0)exit
         break=where+start-1
         options(break:break)=nl
         start=where
      enddo
   endif
   last=len_trim(version)+1
   col=0
   do i=1,len_trim(version)
    col=col+1
    if(version(i:i).eq.' ')last=i
    if(col.gt.76)then
       version(last:last)=nl
       col=0
    endif
   enddo
   print '(a,/,3x,*(a))', 'This file was compiled by :', inset(version)
   if(options.ne.'')then
      print '(*(a))', 'using the options :', inset(options)
   endif
end subroutine platform

function inset(string) result(longer)
character(len=*),intent(in)    :: string
character(len=:),allocatable   :: longer
character(len=*),parameter     :: nl=new_line('a')
integer                        :: i
   longer=''
   do i=1,len(string)
      longer=longer//string(i:i)
      if(string(i:i).eq.nl)then
         longer=longer//'   '
      endif
   enddo
end function inset

function three(in) result(out)
integer(kind=int64),intent(in) :: in
character(len=:),allocatable   :: temp
character(len=:),allocatable   :: out
integer                        :: i
character(len=80)              :: line
   write(line,*)abs(in)
   temp='  '//trim(adjustl(line))
   out=''
   do i=len(temp),3,-3
      out=','//temp(i-2:i)//out
   enddo
   out=trim(adjustl(out(2:)))
   out=merge(' ','-',in>0)//out
end function three

subroutine machineid()
! OS code for machine, not real hardware ID.
! should look something like 566f9e977c3c4c46d993b04f5f57f6a8
integer            :: lun
integer            :: iostat
character(len=256) :: line
   open(newunit=lun,file='/var/lib/dbus/machine-id',iostat=iostat)
   if(iostat.eq.0)then
      read(lun,'(a)',iostat=iostat)line
      if(iostat.eq.0)then
         write(*,'(*(g0))')'pseudo machine-id : ',trim(line)
      endif
   endif
end subroutine machineid

end program test_id

compiler aoccflang520

click to see output...
++ cexpl -c aoccflang520 --skip-asm '--cflags=-cpp -DCOMPILER_=aoccflang520' -e fid.f90
aoccflang520         :     1	STDOUT:$
aoccflang520         :     2	This file was compiled by :$
aoccflang520         :     3	   flang AOCC AOCC_5.2.0-Build#2035 2026_04_10 $
aoccflang520         :     4	using the options :$
aoccflang520         :     5	   -o$
aoccflang520         :     6	   -g$
aoccflang520         :     7	   -fno-crash-diagnostics$
aoccflang520         :     8	   -cpp$
aoccflang520         :     9	   -DCOMPILER_=aoccflang520$
aoccflang520         :    10	   -L./lib$
aoccflang520         :    11	   -Wl,-rpath,./lib$
aoccflang520         :    12	 $
aoccflang520         :    13	 9223372036854775807$
aoccflang520         :    14	 SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type):$
aoccflang520         :    15	 COUNT_MAX(64bit)=      9,223,372,036,854,775,807$
aoccflang520         :    16	 COUNT_RATE(64bit)=     10,000,000$
aoccflang520         :    17	 CURRENT COUNT(64bit)= -0$
aoccflang520         :    18	 9 10 9 10 9 12 10 9 10 9$
aoccflang520         :    19	 $

compiler flangtrunk

click to see output...
++ cexpl -c flangtrunk --skip-asm --cflags= -e fid.f90
flangtrunk           :     1	STDOUT:$
flangtrunk           :     2	This file was compiled by :$
flangtrunk           :     3	   flang version 23.0.0 (https://github.com/llvm/llvm-project.git$
flangtrunk           :     4	   8d61da2569a975346150371cb0d317fb9339be0c) $
flangtrunk           :     5	using the options :$
flangtrunk           :     6	   -o /app/output.s$
flangtrunk           :     7	   -g$
flangtrunk           :     8	   -L./lib$
flangtrunk           :     9	   -Wl,-rpath,./lib$
flangtrunk           :    10	$
flangtrunk           :    11	 9223372036854775807$
flangtrunk           :    12	 SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type):$
flangtrunk           :    13	 COUNT_MAX(64bit)=      9,223,372,036,854,775,807$
flangtrunk           :    14	 COUNT_RATE(64bit)=     1,000,000,000$
flangtrunk           :    15	 CURRENT COUNT(64bit)=  260,190,959,342$
flangtrunk           :    16	 60 40 40 40 40 50 40 50 40 40$
flangtrunk           :    17	$

compiler ifxlatest

click to see output...
++ cexpl -c ifxlatest --skip-asm '--cflags=-fpp -warn all -check all,nouninit -error-limit 1 -O0 -g -assume byterecl -traceback -DCOMPILER_=ifxlatest' -e fid.f90
ifxlatest            :     1	STDOUT:$
ifxlatest            :     2	This file was compiled by :$
ifxlatest            :     3	   Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version$
ifxlatest            :     4	   2025.3.2 Build 20260112 $
ifxlatest            :     5	using the options :$
ifxlatest            :     6	   -g$
ifxlatest            :     7	   -o /app/output.s$
ifxlatest            :     8	   -gxx-name=/opt/compiler-explorer/gcc-13.2.0/bin/g++$
ifxlatest            :     9	   -fpp$
ifxlatest            :    10	   -warn all$
ifxlatest            :    11	   -check all,nouninit$
ifxlatest            :    12	   -error-limit 1$
ifxlatest            :    13	   -O0$
ifxlatest            :    14	   -g$
ifxlatest            :    15	   -assume byterecl$
ifxlatest            :    16	   -traceback$
ifxlatest            :    17	   -DCOMPILER_=ifxlatest$
ifxlatest            :    18	   -L./lib$
ifxlatest            :    19	   -Wl,-rpath,./lib$
ifxlatest            :    20	   -Wl,-rpath,/opt/compiler-explorer/intel-fortran-2025.3.2.25/compiler/latest/lib $
ifxlatest            :    21	 $
ifxlatest            :    22	 9223372036854775807$
ifxlatest            :    23	 SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type):$
ifxlatest            :    24	 COUNT_MAX(64bit)=      9,223,372,036,854,775,807$
ifxlatest            :    25	 COUNT_RATE(64bit)=     1,000,000$
ifxlatest            :    26	 CURRENT COUNT(64bit)=  1,783,217,917,642,439$
ifxlatest            :    27	 1 1 1 1 1 1 1 1 1 1$
ifxlatest            :    28	 $

compiler lfortran0590

click to see output...
++ cexpl -c lfortran0590 --skip-asm '--cflags=--no-style-suggestions --cpp -DCOMPILER_=lfortran0590' -e fid.f90
lfortran0590         :     1	STDOUT:$
lfortran0590         :     2	This file was compiled by :$
lfortran0590         :     3	   LFortran version 0.59.0 $
lfortran0590         :     4	using the options : /opt/compiler-explorer/lfortran/v0.59.0/bin/lfortran$
lfortran0590         :     5	   -o /app/output.s$
lfortran0590         :     6	   --generate-object-code$
lfortran0590         :     7	   --no-style-suggestions$
lfortran0590         :     8	   --cpp$
lfortran0590         :     9	   -DCOMPILER_=lfortran0590$
lfortran0590         :    10	   -L./lib$
lfortran0590         :    11	   -Wl,-rpath,./lib $
lfortran0590         :    12	$
lfortran0590         :    13	 9223372036854775807 $
lfortran0590         :    14	 SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type): $
lfortran0590         :    15	 COUNT_MAX(64bit)=      9,223,372,036,854,775,807 $
lfortran0590         :    16	 COUNT_RATE(64bit)=     1,000,000,000 $
lfortran0590         :    17	 CURRENT COUNT(64bit)=  37,100,504,932,788 $
lfortran0590         :    18	 1932 2108 1916 1845 2429 1763 2015 1853 1957 1712$
lfortran0590         :    19	$
lfortran0590         :    20	No file found with given unit$
lfortran0590         :    21	STDERR:$
lfortran0590         :    22	warning: `--generate-object-code` is deprecated and will be removed in a future release; use `--separate-compilation` instead.$
lfortran0590         :    23	warning: `--generate-object-code` is deprecated and will be removed in a future release; use `--separate-compilation` instead.$

compiler nvfortran_x86_26_3

click to see output...
++ cexpl -c nvfortran_x86_26_3 --skip-asm '--cflags=-Mbackslash -Mpreprocess -DCOMPILER_=nvfortran_x86_26_3' -e fid.f90
nvfortran_x86_26_3   :     1	STDOUT:$
nvfortran_x86_26_3   :     2	This file was compiled by :$
nvfortran_x86_26_3   :     3	   nvfortran 26.3-0 $
nvfortran_x86_26_3   :     4	using the options : /app/example.f90$
nvfortran_x86_26_3   :     5	   -g$
nvfortran_x86_26_3   :     6	   -o /app/output.s$
nvfortran_x86_26_3   :     7	   -Mbackslash$
nvfortran_x86_26_3   :     8	   -Mpreprocess$
nvfortran_x86_26_3   :     9	   -DCOMPILER_=nvfortran_x86_26_3$
nvfortran_x86_26_3   :    10	   -L./lib$
nvfortran_x86_26_3   :    11	   -Wl,-rpath,./lib$
nvfortran_x86_26_3   :    12	 $
nvfortran_x86_26_3   :    13	 9223372036854775807$
nvfortran_x86_26_3   :    14	 SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type):$
nvfortran_x86_26_3   :    15	 COUNT_MAX(64bit)=      9,223,372,036,854,775,807$
nvfortran_x86_26_3   :    16	 COUNT_RATE(64bit)=     10,000,000$
nvfortran_x86_26_3   :    17	 CURRENT COUNT(64bit)= -0$
nvfortran_x86_26_3   :    18	 9 10 9 12 10 9 10 9 10 12$
nvfortran_x86_26_3   :    19	 $

compiler gfortran161

click to see output...
++ cexpl -c gfortran161 --skip-asm '--cflags=-cpp -Wall -Wextra -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -DCOMPILER_=gfortran161' -e fid.f90
gfortran161          :     1	STDOUT:$
gfortran161          :     2	This file was compiled by :$
gfortran161          :     3	   GCC version 16.1.0 $
gfortran161          :     4	using the options :$
gfortran161          :     5	   -fdiagnostics-color=always$
gfortran161          :     6	   -cpp$
gfortran161          :     7	   -imultiarch x86_64-linux-gnu$
gfortran161          :     8	   -iprefix /cefs/38/383ad2f84cbd57a52fd68bbe_consolidated/compilers_c++_x86_gcc_16.1.0/bin/../lib/gcc/x86_64-linux-gnu/16.1.0/$
gfortran161          :     9	   -D COMPILER_=gfortran161$
gfortran161          :    10	   -mtune=generic$
gfortran161          :    11	   -march=x86-64$
gfortran161          :    12	   -g$
gfortran161          :    13	   -g$
gfortran161          :    14	   -Wall$
gfortran161          :    15	   -Wextra$
gfortran161          :    16	   -fno-verbose-asm$
gfortran161          :    17	   -fPIC$
gfortran161          :    18	   -fmax-errors=1$
gfortran161          :    19	   -fbounds-check$
gfortran161          :    20	   -fcheck=array-temps$
gfortran161          :    21	   -fbacktrace$
gfortran161          :    22	   -fcoarray=single$
gfortran161          :    23	   -fpre-include=/usr/include/finclude/x86_64-linux-gnu/math-vector-fortran.h$
gfortran161          :    24	$
gfortran161          :    25	 9223372036854775807$
gfortran161          :    26	 SYSTEM_CLOCK(3) (using integer(kind=int64), result may vary with argument type):$
gfortran161          :    27	 COUNT_MAX(64bit)=      9,223,372,036,854,775,807$
gfortran161          :    28	 COUNT_RATE(64bit)=     1,000,000,000$
gfortran161          :    29	 CURRENT COUNT(64bit)=  275,401,152,216$
gfortran161          :    30	 70 40 40 50 40 40 40 40 40 40$
gfortran161          :    31	$

Hopefully it is straight-forward to see how to customize the script for various needs and to select different compilers. The output does not need to be run through cat -nev to expand non-printable characters and add line numbers to output, or run through sed to prefix the output with the compiler mnemonic, for example.