Fortran Style Guide

NEMO should be developed consistent with the NEMO contributors guide. CanAM and CanCPL adopt the Fortran style guidelines used for CLASSIC model, with the following exceptions:

  1. camelCase is not required; snake_case is allowed

  2. Indentation blocks can be 2 or 4 spaces, but new code must follow the convention used within an individual source code file.

The source code is now required to use the free-form format introduced in Fortran 90; fixed form (Fortran Fortran 77 and earlier) code will not be accepted. See Style Enforcement for info on how the standard is enforced.

Notable differences

For most CanAM developers, the following is a brief (not exhaustive) guide to the more pervasive style changes:

  1. Use free-format Fortran and not fixed form.

    • Denoting comments

      • Comment lines can be initiated anywhere and are denoted by !

      • A C or * in column 1 is not a valid way of indicating a comment line

    • Lines can now be longer than 72 characters (the style guide specifies 120 characters).

      • & is the character which denotes that the current line continues to the next

      • Putting a continuation character in column 6 does not continue the previous line

      call mysub( foo,   ! WRONG
          1       bar,
          &       faz,
          +       baz )
      
      call mysub( foo, & ! Correct!
                  bar, &
                  faz, &
                  baz )
      
    • No arithmetic if statements of the form if (arith_expr) lable1, lable2, lable3 - logical if statements should be used instead.

      ! An arithmetic if statement - WRONG
          if (x) 100, 110, 120
      100 print*, "X is negative"
          go to 200
      110 print*, "X is zero"
          go to 200
      120 print*, "X is positive"
      200 continue
      
      ! A logical if statement - CORRECT
      if (x < 0) then
         print*, "X is negative"
      else if (x == 0) then
         print*, "X is zero"
      else
         print*, "X is positive"
      end if
      
  2. Developers should no longer work in ALL CAPS, and instead use lowercase to improve readability for other developers.

  3. All variables must be explicitly typed.

    • Programs, modules, and interfaces must contain implicit none

    • Subroutines and functions outside of modules must have implicit none

    • Custom implicit rules (e.g. implicit real, (a-b) are not allowed)

  4. Variables in a subroutine or function signature must be declared on their own line with explicit type, intent, and documentation (where “signature” refers to variables/objects sent in through the subroutine/function call.)

    !> Returns the pressure at a given layer
    subroutine mysub(foo, bar, baz)
      integer, intent(in   ) :: foo !< The vertical model index \f$[unitless]\f$
      real,    intent(  out) :: bar !< Pressure of the layer foo \f$[Pa]\f$
      logical, intent(inout) :: baz !< If true, `bar` is defined at the interface, otherwise
                                    !! at the midpoint of the cell.
    end subroutine mysub
    

    Developers unfamiliar with the intent keyword are encouraged to reference online resources for detailed examples or explanations (i.e. see here, here or here) but in summary, intent is used to define the intended use of an argument within a function or subroutine, and helps the compiler pick up potential bugs. When defining the intent, it can take the following three values - in, out, or inout, where:

    • intent(in) - the argument should only be used to pass information into the routine (i.e. the value should not be changed)

    • intent(out) - the argument should only be used to pass information out of the routine (i.e. it is meant to just hold a result)

    • intent(inout)- the argument can be used to carry information into and out of the routine (i.e. sending a variable that is used in a calculation and then updated)

    Additionally, it should be noted that:

    • while intent helps compilers pick up potential bugs, when routines don’t have explicit interface blocks, or exist within a module, they are unable to pick up intent violations in nested routine calls as they have no way of checking that the routine signatures match.

    • when not used, the compilers generally assume that the variables should be treated as intent(inout) - but it must be explicitly stated that they are not exactly equivalent (interested readers who wish to go down a technical rabbit hole can begin here) .

  5. Array variables should be declared using the dimension attribute

    real :: array(ni,nj,nk)            ! WRONG
    real, dimension(ni,nj,nk) :: array ! Correct
    
  6. Variables must be declared using :: to separate the variable type and name, e.g.

    integer foo    ! WRONG
    integer :: foo ! Correct
    
  7. No new Holleriths can be introduced - developers should use character arrays to store strings instead, e.g.

    integer :: string_var = 3HPSL            ! WRONG
    character(len=3) :: string_var = 'PSL'   ! Correct
    
  8. No new go to statements can be added.

    • with the new standards, it has become highly recommended that go to statements are avoided, and thus new instances of go tos will not be accepted in any new CanAM code.

    • if working within a pre-existing looping structure and wish to use go to to skip some code within the loop, see Fortran’s cycle or exit commands. For other situations, developers will need to reconsider the control flow of their program. Different branching/loop structures should be used to avoid the need for these statements. For additional guidance on these structures see Fortran - Loops and Fortran - Decisions here

Style Enforcement

During the initial syntax upgrade, all code within CanAM was updated to free-format Fortran and checked for compliance with the CLASSIC style guidelines with the two exceptions noted above. Going forward, all new code will be assessed to confirm that it meets the standards laid out above.

Note

Contributions with style violations will not be accepted into the main branch

To make sure the code meets the required standards, it will be checked both

  • automatically by a code analysis tool (referred to as a linter) that checks for obvious style violations, and

  • manually during merge request review

Linter Checks

A large portion of the style requirements will need to be enforced through the code review process, but during the automatic checking, the linter breaks things into “Warnings” and “Errors”, where the errors cause the checks to “Fail”, and the warnings do not, but the code reviewers may still ask the developer to fix the code to avoid the warnings.

Errors:

  • implicit variables - i.e. the code does not use implicit none

  • intent missing from required variables

  • variables defined without the :: separator

  • fixed form specific syntax is used - i.e. using C or c for comment lines

  • new arithmetic if statements added

  • new hollerith variables added

  • new go to statements added

    • note that go to statements that were not able to be trivially removed were left and will be kept until work is done to remove them. New go to statements are not accepted.

Warnings:

  • multiple variables defined on one line

    • this is acceptable for local variables, such as index vars like i, j, k, which is why it is just a warning.

Using the linter manually

For developers who would like to check their code manually, the linter can also be invoked interactively, and documentation has been provided in to help in this process. As an example, to check the code within CanAM, provided the developer is working within a suitable python environment, all that needs to be done is:

cd path/to/CanAM
python ../CCCma_tools/linter/fast.py    # or python3, depending on your environment

Notes on Newer Fortran Standards

It should be noted that while Fortran 90 represents a major revision to Fortran standards there have been further revisions to the language since, with the most recent standard being published in 2018. Specifically, further revisions to the standard include:

Unlike the change from Fortran 77 to Fortran 90, the syntax of the language remains similar between all the standards. All standards subsequent to Fortran 90 are fully backwards compatible with the primary differences being the inclusion of new features which include support for more object-oriented programming, portability across machines, and interoperability with C. Developers are encouraged to learn more about these standards and incorporate them where appropriate in their own development.