Formatting Fortran (.f90) Code for Auto-Documentation
This document describes how a Fortran 90 source file must be formatted so it
can be automatically documented by the Fortran Sphinx domain. Every rule below
was checked against the parser, and every “generated output” block is what
f:automodule / f:autoroutine really produce for the snippet above it.
General Rules
Documentation is extracted from ordinary Fortran comments (
!). No special marker is needed. In particular!>and!!(Doxygen style) are not recognised and would be copied literally into the output.The description of a module, function, subroutine, interface, derived type or program is the block of comment lines immediately following its opening statement, i.e. inside the entity, before any
use,implicit noneor declaration.The description of a variable, argument or derived-type component is the comment written at the end of its declaration line, optionally followed by continuation comment lines.
Comments are written in reStructuredText, so lists, math, admonitions and cross-reference roles all work.
Everything that can be deduced from the code is taken from the code: names, order of arguments, types, kinds of character strings, array shapes,
intent,optional,parameter, default values from initialisers,bind(c)bindings,usestatements. Do not repeat it in the comments.
The single most frequent mistake is to write the comment before the entity, as in Doxygen or Python. The parser never looks there:
!> Computes the double of x.
!! (comment placed BEFORE the routine)
function twice(x) result(y)
real(8) :: x !Input value
real(8) :: y !Twice x
y = 2*x
end function twice
produces no description at all:
.. f:function:: twice(x)
:p real x: Input value
:r real y: Twice x
whereas the same comment moved one line down is picked up:
function twice(x) result(y)
!Computes the double of x.
real(8) :: x !Input value
real(8) :: y !Twice x
y = 2*x
end function twice
.. f:function:: twice(x)
Computes the double of x.
:p real x: Input value
:r real y: Twice x
Modules
The comment lines directly under the module statement form the module
description. A line whose text starts with :synopsis: is not part of the
description: it becomes the one-line summary displayed in the module index
(and in the :synopsis: option of f:module).
module m
!:synopsis: Short text shown in the module index
!Long description of the module, with rst markup:
!
! * first point
! * second point
implicit none
real(8) :: pi !Circle constant
end module m
generates
.. f:module:: m
:synopsis: Short text shown in the module index
.. rubric:: Description
Long description of the module, with rst markup:
* first point
* second point
.. rubric:: Quick access
:Variables: :f:var:`pi`
.. rubric:: Variables
.. f:variable:: pi
Circle constant
:Type: real
Without a :synopsis: line, the synopsis is the first paragraph of the
description (at most the first four lines). ED_INPUT_VARS (see
Examples) starts like this:
MODULE ED_INPUT_VARS
!:synopsis: User-accessible input variables
!Contains all global input variables which can be set by the user through the input file.
!...
!
USE SF_VERSION
USE SF_PARSE_INPUT
...
The use statements are listed automatically under Used modules (for the
modules that are themselves documented in the project) and External modules
(for the others), together with the only lists and renamings.
Note
Preprocessor directives such as #ifdef _MPI are not evaluated: the
modules used in all the branches are listed (this is why mpi and
sf_mpi appear in the use field of ed_read_input).
Functions and Subroutines
A routine is documented by:
a description block right under its signature line;
one comment at the end of the declaration of each argument, and of the result variable of a function.
Here is linspace of SF_ARRAYS (shortened):
function linspace(start,stop,num,istart,iend,mesh) result(array)
!
!Returns an array of evenly spaced numbers over a specified interval.
!Returns :f:var:`num` evenly spaced samples, calculated over the interval [:f:var:`start`, :f:var:`stop`].
!The start and end points of the interval can optionally be excluded.
!
real(8) :: start !Starting value of the sequence
real(8) :: stop !End value of the sequence
integer :: num !Number of samples to generate
logical,optional :: istart !If :code:`.true.`, :f:var:`start` is included in the resulting array. Default :code:`.true.`
logical,optional :: iend !If :code:`.true.`, :f:var:`stop` is included in the resulting array. Default :code:`.true.`
real(8),optional :: mesh !If present, the step is saved in this variable
real(8) :: array(num) !Contains :f:var:`num` equally spaced samples in the interval
![:f:var:`start`, :f:var:`stop`], left/right open or closed depending on
!:f:var:`istart` and :f:var:`iend`
...
The generated text is:
.. f:function:: linspace(start, stop, num[, istart, iend, mesh])
Returns an array of evenly spaced numbers over a specified interval.
Returns :f:var:`num` evenly spaced samples, calculated over the interval [:f:var:`start`, :f:var:`stop`].
The start and end points of the interval can optionally be excluded.
:p real start: Starting value of the sequence
:p real stop: End value of the sequence
:p integer num: Number of samples to generate
:o logical istart: If :code:`.true.`, :f:var:`start` is included in the resulting array. Default :code:`.true.`
:o logical iend: If :code:`.true.`, :f:var:`stop` is included in the resulting array. Default :code:`.true.`
:o real mesh: If present, the step is saved in this variable
:r real array(num): Contains :f:var:`num` equally spaced samples in the interval [:f:var:`start`, :f:var:`stop`], left/right open or closed depending on :f:var:`istart` and :f:var:`iend`
Points worth noting:
the signature
linspace(start, stop, num[, istart, iend, mesh])is built from the declarations: the arguments that have theoptionalattribute are enclosed in square brackets. Nothing has to be written in the comment;the fields are
:p(required argument),:o(optional argument) and:r(result of the function, that is the variable named afterresult(...)or the function itself), followed by the type, the shape (array(num)) and, when they exist, the attributes in brackets ([in],[inout], …);within the description, the name of an argument written with
:f:var:`name`becomes a link to that argument.
Ways to describe an argument
Form |
Where |
Behaviour |
|---|---|---|
Inline comment |
|
Recommended. Wins over the other two forms. |
|
|
The line is removed from the description and its text is
appended to the inline description of |
Header line |
|
Copied to the description of |
Example mixing the three forms:
subroutine axpy(a, x, y)
!Computes :math:`y \leftarrow a x + y`.
!
!@param a: scalar factor
!
!y: header form (the line is also kept in the text!)
real(8), intent(in) :: a
real(8), intent(in) :: x(:)
real(8), intent(inout) :: y(:) !Vector updated in place
y = a*x + y
end subroutine axpy
.. f:subroutine:: axpy(a, x, y)
Computes :math:`y \leftarrow a x + y`.
y: header form (the line is also kept in the text!)
:p real a [in]: scalar factor
:p real x(:) [in]:
:p real y(:) [inout]: Vector updated in place
Warning
A header line starting with the name of an argument followed by a
space or punctuation is read as the description of that argument, even
if it was meant as ordinary prose: a sentence like x is scaled in place
would become the description of x (and would also stay in the text).
Start sentences with another word. The positional form $1, $2,
… that appears in the source of the parser does not currently match
(see Known limitations); do not rely on it.
Continuation lines
A description may continue on the comment lines that follow the declaration. Two details matter:
consecutive lines are joined without any separator, so the previous line must end with a space, or the continuation must start with one;
a bare
!(nothing after it) closes the description. That is why the input variables ofED_INPUT_VARSare each followed by an empty!.
integer :: bad !First line
!second line, no leading space
integer :: good !First line
! second line, with a leading space
:f:var:`bad`, :f:var:`good`
.. f:variable:: bad
First linesecond line, no leading space
:Type: integer
.. f:variable:: good
First line second line, with a leading space
:Type: integer
Call relationships
The :calledfrom: and :callto: fields (aliases :from: and
:to:) are ordinary fields of the description block and are shown as
Called from / Call to lists:
subroutine solve(a, b, x)
!Solves the linear system.
!
!:calledfrom: main
!:callto: factorize
...
They are written by hand. The parser also builds the lists of callers
and callees on its own (attributes callto and callfrom of each routine,
see build_callfrom_index())
but does not print them.
Where the description block must be
The block must be the very first thing after the signature (which may itself
span several lines with &). If a use or implicit none comes first,
the description is silently lost:
subroutine s1(x)
use iso_fortran_env
!Described after a use statement: LOST
real(8) :: x !An x
end subroutine s1
subroutine s2(x)
!Described right after the signature: kept
use iso_fortran_env
real(8) :: x !An x
end subroutine s2
.. f:subroutine:: s1(x)
:p real x: An x
:use: :f:mod:`iso_fortran_env`
.. f:subroutine:: s2(x)
Described right after the signature: kept
:p real x: An x
:use: :f:mod:`iso_fortran_env`
Module Variables
Two styles are available and can be mixed.
Same-line style, for short descriptions:
real(8) :: pi !Circle constant
Trailing-bang style, used by ED_INPUT_VARS. The declaration line ends
with an empty ! and the description follows on the next comment lines,
until a bare ! (or any non-comment line) ends it:
real(8) :: beta !
!Inverse temperature.
! :Default beta:`1000d0`
!
integer :: nloop !
!Maximum number of loops. Uses a list:
! * :code:`0` : none
! * :code:`1` : all
! :Default nloop:`100`
!
integer :: forgotten
!This comment is NOT attached (declaration has no trailing bang)
:f:var:`beta`, :f:var:`nloop`, :f:var:`forgotten`
.. f:variable:: beta
Inverse temperature.
:Type: real
:Default: 1000d0
.. f:variable:: forgotten
:Type: integer
.. f:variable:: nloop
Maximum number of loops. Uses a list:
* :code:`0` : none
* :code:`1` : all
:Type: integer
:Default: 100
The rules that this example illustrates:
the trailing
!on the declaration line is mandatory in this style: it is what tells the parser that a description follows.forgottengets none;a line of the form
:Label varname:`value`is removed from the text and shown as a:Label:field of the variable. This is how:Default:is produced inED_INPUT_VARS, but any label works (:Units x:`eV`). Only the first such line of a variable is kept; further ones are dropped;lines starting with
*are surrounded with blank lines, so bullet lists work. Other lines of a variable description are joined into a single line (see continuation above), which means that literal blocks or multi-paragraph text cannot be written there;the type is printed with its shape appended:
:Type: real(5)forreal(c_double), dimension(5) :: Ulocis a real array of 5 elements, notkind=5. The kind of a numeric type is not shown, the length of acharacteris;attributes (
save,parameter,public, …) appear in:Attributes:and abind(c, name="...")becomes the:Bindings:field; the initial value of the declaration, if any, becomes:Default:— so do not also write:Default x:for a variable that has an initialiser, or it will be printed twice.Module variables are listed in alphabetical order, routines and types in source order.
Here are real variables from ED_INPUT_VARS:
integer(c_int),bind(c, name="Nbath") :: Nbath !
!Number of bath sites:
! * :f:var:`bath_type` = :code:`normal` : number of bath sites per orbital
! * :f:var:`bath_type` = :code:`hybrid` : total number of bath sites
! * :f:var:`bath_type` = :code:`replica/general` : number of replicas
! :Default Nbath:`6`
!
.. f:variable:: nbath
Number of bath sites:
* :f:var:`bath_type` = :code:`normal` : number of bath sites per orbital
* :f:var:`bath_type` = :code:`hybrid` : total number of bath sites
* :f:var:`bath_type` = :code:`replica/general` : number of replicas
:Type: integer
:Bindings: Language = **c**, Name = **nbath**
:Default: 6
Derived Types
Derived types are documented like modules of variables: a description block
under the type line, then one inline comment per component.
type point
!A point in the plane.
real(8) :: x !Abscissa
real(8) :: y !Ordinate
end type point
.. rubric:: Types
.. f:type:: point
A point in the plane.
:f real x: Abscissa
:f real y: Ordinate
Warning
Only the plain form type name gets a description and component
descriptions. With type :: name, type, public :: name or
type, extends(base) :: name the type is still listed, but without any
description:
.. rubric:: Types
.. f:type:: point
:f real x:
:f real y:
Prefer type name, and control the visibility with a separate
public :: name statement placed after the type definition (see
visibility).
Interfaces
For a generic interface, put the description right under the interface
line. The arguments are collected from all the procedures listed after
module procedure: when the implementations differ, the types are joined
(integer,real) and differing shapes are displayed as (various shapes).
The individual procedures are documented as usual.
interface twice
!Generic interface: doubles an integer or a real.
module procedure twice_i, twice_r
end interface twice
.. f:interface:: twice(x)
Generic interface: doubles an integer or a real.
:p integer,real x: Input
:r integer,real y: Twice the input
Programs
A main program is documented like a routine (description block under the
program line) and is rendered with f:program. Its use statements
are printed in a :use: field.
program main
!Main program description
use mymodule
end program main
Visibility (public / private)
Only public entities are documented. The parser follows the private and
public statements and attributes of the module:
module m
implicit none
private
public :: shown
real(8) :: shown !Public through the public statement
real(8) :: secret !Private by default
end module m
:f:var:`shown`
.. f:variable:: shown
Public through the public statement
:Type: real
:Attributes: public
Two things to know:
for a derived type in a module made private by a bare
privatestatement, thepublic :: namestatement must come after the type definition. Placed before, the type is hidden;the options
:members:,:undoc-members:and:forceadd-members:off:automodulechange the selection (see Fortran Autodoc).
Using reStructuredText in comments
All the usual constructs can be used in descriptions:
You write in the comment |
Result |
|---|---|
|
inline literal, as in |
|
inline LaTeX maths |
|
links to variables, routines and modules of the Fortran domain |
|
bullet list (blank lines are added around each item) |
|
admonition. In a variable description, put it after the bullets; its lines are joined into one, which is fine for a paragraph |
|
field of a routine description |
Common indentation is removed, so the comment may be indented as the code is.
In descriptions of modules, routines and types the line structure is kept
(a bare ! is a blank line), so these blocks accept any reST, including
literal blocks and several paragraphs.
Checklist: why is my description missing?
Symptom |
Cause |
Fix |
|---|---|---|
Routine or module has no description |
Comment written before the signature, or after a |
Move it to the lines right after the signature |
Output contains |
|
Use plain |
Module variable has no description ( |
Description on the next lines but the declaration has no trailing |
Add the trailing |
Description of a variable is cut |
A bare |
Use |
Two words glued together |
Continuation line without leading space |
Start the continuation with a space |
Type without description or components |
|
Use |
Type or variable missing |
|
|
|
Initialiser in the declaration and |
Keep only one |
Helper variables such as |
They are public and undocumented |
Make them |
Cross-reference not linked |
Target not documented in this project, or wrong case |
Document it or use the exact lower-case name |
Summary
If a Fortran entity (module, variable, function, type) has a properly formatted comment block using reStructuredText, inside the entity for modules, routines and types, and at the end of the declaration for variables, it will be correctly parsed and indexed by Sphinx.