Getting started#

Difficulty: Beginner

This tutorial provides a gentle introduction to the use of TOML Fortran. It will deal with reading as well as creating TOML data structures using the high-level build interface and discuss how to obtain a data structure from a TOML document or turn a data structure to TOML document again.

For this project we will be working with fpm, however you can use any build tool you are familar with, checkout the integration guide to find a matching setup. We start with creating a minimal package manifest to use TOML Fortran in our fpm project.

fpm.toml#
name = "demo"

[dependencies]
toml-f.git = "https://github.com/toml-f/toml-f.git"

The public TOML Fortran API is defined in the tomlf module, we will only use this module for this entire course. The main data structures we are going to interact with are toml_table and toml_array instances, which we can conveniently manipulate with the generic interface get_value.

src/reader.f90#
module reader
  use tomlf, only : toml_table, toml_array, get_value, len
  implicit none
  private

  public :: read_data

contains

  subroutine read_data(table, title, spectrum)
    type(toml_table), intent(inout) :: table
    character(len=:), allocatable, intent(out) :: title
    real, allocatable, intent(out) :: spectrum(:)

    type(toml_table), pointer :: child
    type(toml_array), pointer :: array
    logical :: reverse
    integer :: ival

    ! Get character value from entry "title"
    call get_value(table, "title", title)

    ! Get subtable reference from entry "spectrum"
    call get_value(table, "spectrum", child)

    ! Get array reference from entry "data"
    call get_value(child, "data", array)

    ! Read all values from the data array
    allocate(spectrum(len(array)))
    do ival = 1, size(spectrum)
      call get_value(array, ival, spectrum(ival))
    end do

    ! Data is stored in reverse order
    call get_value(child, "reverse", reverse, .false.)
    if (reverse) spectrum(:) = spectrum(size(spectrum):1:-1)
  end subroutine read_data

end module reader

Note that we declare the TOML data structure as mutable, i.e. intent(inout) rather than just intent(in), as the get_value interface can modify the data structure. We start with a simple test program which is not actually reading any TOML document, but just passing an empty table to our reader.

app/defaults.f90#
program defaults
  use reader, only : read_data
  use tomlf, only : toml_table
  implicit none

  type(toml_table), allocatable :: table
  character(len=:), allocatable :: title
  real, allocatable :: spectrum(:)

  table = toml_table()

  call read_data(table, title, spectrum)

  if (allocated(title)) then
    print '(a)', "Title: '"//title//"'"
  end if

  print '(*(g0, 1x))', "Entries:", size(spectrum)
  if (size(spectrum) > 0) then
    print '(*(g0, 1x))', "Spectrum:", spectrum
  end if
end program defaults

The get_value interface for processing the TOML data structure ensures that the data structure is complete throughout the whole process of reading it and will add the requested nodes if there are not present or will fill them in with default values. Convince yourself that the empty table indeed changed while reading by passing a serializer to it.

block
  use tomlf, only : toml_serialize

  print '(a)', "# Final TOML data structure"
  print '(a)', toml_serialize(table)
end block
! Expected output:
! # Final TOML data structure
! [spectrum]
! data = [ ]
! reverse = false

This behavior is very convenient because it allows us to define our default values while defining how we read the TOML data structure.

Note

The get_value build interface is only one way of accessing the TOML data structure provided by TOML Fortran. It takes an opinionated approach towards reading and modifying the data structure, which is suitable the majority of applications.

Now we will actually read a TOML document and pass it to our reader.

input.toml#
title = "Example"
spectrum.data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

We adapt our command line driver to read the file input.toml and output the values as before

app/readin.f90#
program readin
  use reader, only : read_data
  use tomlf, only : toml_table, toml_parse, toml_error
  implicit none

  type(toml_table), allocatable :: table
  character(len=:), allocatable :: title
  real, allocatable :: spectrum(:)


  block
    integer :: io
    type(toml_error), allocatable :: error

    open(file="input.toml", newunit=io, status="old")
    call toml_parse(table, io, error)
    close(io)
    if (allocated(error)) then
      print '(a)', "Error: "//error%message
      stop 1
    end if
  end block

  call read_data(table, title, spectrum)

  if (allocated(title)) then
    print '(a)', "Title: '"//title//"'"
  end if

  print '(*(g0, 1x))', "Entries:", size(spectrum)
  if (size(spectrum) > 0) then
    print '(*(g0, 1x))', "Spectrum:", spectrum
  end if
end program readin

Running the program with fpm shows that we were able to read the correct values from the document

❯ fpm run readin
Title: 'Example'
Entries: 10
Spectrum: 0.00000000 1.00000000 2.00000000 3.00000000 4.00000000 5.00000000 6.00000000 7.00000000 8.00000000 9.00000000

You can again use the serializer to write the final data structure, if you want to check whether the get_value interface has added default values.

Important

In this tutorial you have learned out to read simple data structures from TOML documents. You can now

  • define the logic to read data from TOML structures

  • provide default values in your parser as you define the input structure

  • read an actual TOML document from a file

  • write TOML documents from your data structures