Serialisierbare Basisklasse
Serialisierbare Basisklasse#
Dieses Rezept zeigt, wie eine serialisierbare Klasse auf TOML Fortran basierend konstruiert werden kann. Momentan ist TOML Fortran selbst keine Klasse als Basis für eine serialisierbare Klasse, daher werden wir eine Loader- und Dumper-Schnittstelle definieren, um eine Datei oder eine verbundene Unit in eine Datenstruktur zu konvertieren. Die abstrakte Basisklasse implementiert die Verarbeitung der Datei oder der Unit in eine TOML Datenstruktur und leitet diese an eine deferred Prozedur weiter, die in der Klasse implementiert wird um, ein Mapping von der TOML Datenstruktur and zurück zu definieren. Diese Art von Datenstruktur kann in verschiedenen Kontexten genutzt werden and einfach in TOML und zurück transferiert werden.
Bemerkung
TOML Fortran könnte in Zukunft selbst eine solche abstrakte Basisklasse bereitstellen.
Die Basisklasse kann wie folgt definiert werden
!> Definition of configuration data with serde properties.
!> Each data record knows how to serialize and deserialize itself.
module serde_class
use serde_error, only : error_type, fatal_error
use tomlf, only : toml_table, toml_error, toml_load, toml_dump
implicit none
private
public :: serde_record
!> Serializable and deserializable configuration data record
type, abstract :: serde_record
contains
!> Reading of configuration data
generic :: load => load_from_file, load_from_unit, load_from_toml
!> Read configuration data from file
procedure, private :: load_from_file
!> Read configuration data from formatted unit
procedure, private :: load_from_unit
!> Read configuration data from TOML data structure
procedure(load_from_toml), deferred :: load_from_toml
!> Writing of configuration data
generic :: dump => dump_to_file, dump_to_unit, dump_to_toml
!> Write configuration data to file
procedure, private :: dump_to_file
!> Write configuration data to formatted unit
procedure, private :: dump_to_unit
!> Write configuration data to TOML data structure
procedure(dump_to_toml), deferred :: dump_to_toml
end type serde_record
abstract interface
!> Read configuration data from TOML data structure
subroutine load_from_toml(self, table, error)
import :: serde_record, toml_table, error_type
!> Instance of the configuration data
class(serde_record), intent(inout) :: self
!> Data structure
type(toml_table), intent(inout) :: table
!> Error handling
type(error_type), allocatable, intent(out) :: error
end subroutine load_from_toml
!> Write configuration data to TOML datastructure
subroutine dump_to_toml(self, table, error)
import :: serde_record, toml_table, error_type
!> Instance of the configuration data
class(serde_record), intent(in) :: self
!> Data structure
type(toml_table), intent(inout) :: table
!> Error handling
type(error_type), allocatable, intent(out) :: error
end subroutine dump_to_toml
end interface
contains
!> Read configuration data from file
subroutine load_from_file(self, file, error)
!> Instance of the configuration data
class(serde_record), intent(inout) :: self
!> File name
character(len=*), intent(in) :: file
!> Error handling
type(error_type), allocatable, intent(out) :: error
type(toml_error), allocatable :: parse_error
type(toml_table), allocatable :: table
call toml_load(table, file, error=parse_error)
if (allocated(parse_error)) then
allocate(error)
call move_alloc(parse_error%message, error%message)
return
end if
call self%load(table, error)
if (allocated(error)) return
end subroutine load_from_file
!> Read configuration data from file
subroutine load_from_unit(self, unit, error)
!> Instance of the configuration data
class(serde_record), intent(inout) :: self
!> File name
integer, intent(in) :: unit
!> Error handling
type(error_type), allocatable, intent(out) :: error
type(toml_error), allocatable :: parse_error
type(toml_table), allocatable :: table
call toml_load(table, unit, error=parse_error)
if (allocated(parse_error)) then
allocate(error)
call move_alloc(parse_error%message, error%message)
return
end if
call self%load(table, error)
if (allocated(error)) return
end subroutine load_from_unit
!> Write configuration data to file
subroutine dump_to_file(self, file, error)
!> Instance of the configuration data
class(serde_record), intent(in) :: self
!> File name
character(len=*), intent(in) :: file
!> Error handling
type(error_type), allocatable, intent(out) :: error
integer :: unit
open(file=file, newunit=unit)
call self%dump(unit, error)
close(unit)
if (allocated(error)) return
end subroutine dump_to_file
!> Write configuration data to file
subroutine dump_to_unit(self, unit, error)
!> Instance of the configuration data
class(serde_record), intent(in) :: self
!> Formatted unit
integer, intent(in) :: unit
!> Error handling
type(error_type), allocatable, intent(out) :: error
type(toml_table) :: table
table = toml_table()
call self%dump(table, error)
if (.not.allocated(error)) then
call toml_dump(table, unit, error)
end if
end subroutine dump_to_unit
end module serde_class
Wir definieren auch einen Fehler-Handler, der die Fehlermeldung speichert und den Fehlerstatus durch seinen Allokationstatus signalisiert.
!> Central registry for error codes
module serde_error
implicit none
private
public :: error_type, fatal_error
!> Possible error codes
type :: enum_stat
!> Successful run
integer :: success = 0
!> Internal error:
integer :: fatal = 1
end type enum_stat
!> Actual enumerator for return states
type(enum_stat), parameter :: serde_stat = enum_stat()
!> Error message
type :: error_type
!> Error code
integer :: stat
!> Payload of the error
character(len=:), allocatable :: message
end type error_type
contains
!> A fatal error is encountered
subroutine fatal_error(error, message, stat)
!> Instance of the error
type(error_type), allocatable, intent(out) :: error
!> A detailed message describing the error and (optionally) offering advice
character(len=*), intent(in), optional :: message
!> Overwrite of the error code
integer, intent(in), optional :: stat
allocate(error)
if (present(stat)) then
error%stat = stat
else
error%stat = serde_stat%fatal
end if
if (present(message)) then
error%message = message
else
error%message = "Fatal error"
end if
end subroutine fatal_error
end module serde_error
Ein Beispiel für eine serialisierbare Klasse basierend auf der oben gegebenen Basisklasse ist unten aufgeführt.
module demo_serde
use serde_class, only : serde_record
use serde_error, only : error_type, fatal_error
use tomlf, only : toml_table, get_value, set_value
implicit none
private
public :: example_record
type, extends(serde_record) :: example_record
integer :: nrun
real :: alpha
character(len=:), allocatable :: label
contains
!> Read configuration data from TOML data structure
procedure :: load_from_toml
!> Write configuration data to TOML data structure
procedure :: dump_to_toml
end type example_record
contains
!> Read configuration data from TOML data structure
subroutine load_from_toml(self, table, error)
!> Instance of the configuration data
class(example_record), intent(inout) :: self
!> Data structure
type(toml_table), intent(inout) :: table
!> Error handling
type(error_type), allocatable, intent(out) :: error
integer :: stat
call get_value(table, "nrun", self%nrun, 10, stat=stat)
if (stat /= 0) then
call fatal_error(error, "Invalid entry for number of runs")
return
end if
call get_value(table, "alpha", self%alpha, 1.0, stat=stat)
if (stat /= 0) then
call fatal_error(error, "Invalid entry for alpha parameter")
return
end if
call get_value(table, "label", self%label, stat=stat)
if (stat /= 0) then
call fatal_error(error, "Invalid entry for data label")
return
end if
end subroutine load_from_toml
!> Write configuration data to TOML datastructure
subroutine dump_to_toml(self, table, error)
!> Instance of the configuration data
class(example_record), intent(in) :: self
!> Data structure
type(toml_table), intent(inout) :: table
!> Error handling
type(error_type), allocatable, intent(out) :: error
call set_value(table, "nrun", self%nrun)
call set_value(table, "alpha", self%alpha)
if (allocated(self%label)) then
call set_value(table, "label", self%label)
end if
end subroutine dump_to_toml
end module demo_serde
Die definierte Datenklasse kann in einer Anwendung einfach aus einer Datei geladen werden, während die tatsächliche Implementierung sich nicht um das Lesen der TOML Datenstruktur kümmert, sondern kann sich auch darauf verlassen, dass wenn die Konfigurationsdatei gültiges TOML war, eine Datenstruktur zum Lesen bereitsteht.