A parameter list is a hierarchical data structure consisting of a collection
of parameter name/value pairs. A value can be a scalar or array of arbitrary
type, including a parameter list itself. Parameter lists are intended as a
convenient way to pass information between different program units. The
module parameter_list_type
implements the data structure and basic methods
for working with it, and the module parameter_list_json
provides additional
procedures for parameter list input/output using JSON-format text.
A parameter list is a hierarchical data structure consisting of a collection of parameter name/value pairs. A value can be a scalar or array of arbitrary type and kind, including a parameter list itself, termed a sublist parameter. Parameter lists are meant for passing information between different program units. Long argument lists can be shortened by gathering some of the arguments into a parameter list and passing it instead. Sublists can be used for arguments intended to be passed to lower-level procedures, and so forth. While a parameter list can be manually populated through a sequence of calls, it is more easily defined from JSON input text. When used in this way, a parameter list provides a powerful and flexible means for distributing program input to the different components of a program.
Parameter lists are intended to pass lightweight data: scalars or (very) small arrays, typically of intrinsic types. Values of arbitrary derived type can be used but take great caution when doing so because the set/get methods make shallow copies of the value; see the caution on values below. Also such values cannot be input/output using JSON text.
This implementation is inspired by, and modeled after, the
Teuchos::ParameterList
C++ class from Trilinos.
Why not use a generic JSON data structure?
The parameter list data structure is similar to that of JSON, but there are some important differences that make parameter lists far better suited to Fortran use than a generic JSON data structure. Chief among these is that parameter list array values are Fortran arrays, whereas JSON allows jagged arrays with values of differing types. This makes array access natural and far simpler with parameter lists than it would be with JSON.
A simple example will illustrate some basic usage.
use parameter_list_type
type(parameter_list) :: plist
real :: p
character(:), allocatable :: f
!! Parameter lists come into existence well-defined and empty.
!! Define some parameters; note the different types and ranks.
call plist%set('page', 3)
call plist%set('size', 1.4)
call plist%set('color', 'blue')
call plist%set('boundingbox', [10,10,90,90])
call plist%set('crop', .true.)
!! Replace an existing parameter value with a different value
!! of different type, but same rank.
call plist%set('size', 'default')
!! Retrieve a specific parameter value; its type must match P.
call plist%get('size', p)
!! Retrieve a parameter that is not defined;
!! it is created with the specified default value
call plist%get('font', f, default='courier')
!! Create a sublist parameter named 'picture'
sublist => plist%get('picture')
!! Define a parameter in the sublist
call sublist%set('origin', [1.0, 2.0])
Instead of populating a parameter list through set calls, the parameters
can be read from a JSON format text file. Suppose the file input.json
contains the following text:
{
"page": 3, "size": 1.4, "color": "blue",
"boundingbox": [10,10,90,90], "crop": true,
"picture": { "origin": [1.0, 2.0] }
}
Then this code will populate a parameter list with the same values.
use parameter_list_type
use parameter_list_json
type(parameter_list), pointer :: plist
character(:), allocatable :: errmsg
integer :: unit
open(newunit=unit,file='input.json',action='read',access='stream')
call parameter_list_from_json_stream(unit, plist, errmsg)
The two methods can also be combined: a parameter list read from a file can be modified with set methods, and an existing parameter list can be added to with parameters read from a file.
The derived type parameter_list
implements the parameter list data
structure. It has the following properties.
Scalar assignment is defined for parameter_list
variables with the
expected semantics. The lhs parameter list is first deleted, and then
defined with the same parameters and values as the rhs parameter list,
becoming an independent copy of the rhs parameter list; but see the
caution below on derived type values.
The structure constructor parameter_list()
evaluates to an empty
parameter list, and parameter_list
variables come into existence as
empty parameter lists.
parameter_list
objects are properly finalized when they are deallocated
or otherwise cease to exist.
Many of the following subroutines have the optional intent-out arguments
stat
and errmsg
. If the integer stat
is present, it is assigned
the value 0 if no error occurs; otherwise it is assigned a non-zero value
and the allocatable deferred-length character string errmsg
, if present,
is assigned an explanatory message. If stat
is not present and an error
occurs, the error message is written to the preconnected error unit and
program execution is terminated.
set(name, value [,stat [,errmsg]])
Define a parameter with the specified name
and assign it the specified
value
, which may be a scalar, or rank-1 or rank-2 array of any type.
A copy of the passed value, as created by sourced allocation, is stored
in the parameter list; see the caution below for derived type values.
If the parameter already exists, it must not be a sublist parameter and
its existing value must have the same rank as value
, but not
necessarily the same type; its value is overwritten with value
.
get(name, value [,default] [,stat [,errmsg]])
Retrieve the value of the parameter name
. A copy of the value is
returned in value
, which may be a scalar, or rank-1 or rank-2 array
of the following intrinsic types: integer(int32)
, integer(int64)
,
real(real32)
, real(real64)
, default logical
, and default
character
. The kind parameters are those from the intrinsic module
iso_fortran_env
, and should cover the default integer and real kinds,
as well as double precision. An array value
must be allocatable and
a character value
must be deferred-length allocatable. In these latter
cases, value
is allocated with the proper size/length to hold the
parameter value. If present, the optional argument default
must have
the same type, kind, and rank as value
. If the named parameter does
not exist, it is created with the value prescribed by default
, and
that value is returned in value
. It is an error if the named parameter
does not exist and default
is not present. It is an error if the
named parameter is a sublist. It is an error if the type, kind, and rank
of value
does not match the stored value of the named parameter. Use
get_any
when the type of the parameter value is not one of those
handled by this method.
get_any(name, value [,default] [,stat [,errmsg]])
Retrieves the value of the parameter name
. A copy of the value is
returned in value
, which is an allocatable class(*)
variable
or rank-1 or rank-2 array. This is a more general version of get
that can retrieve any type of parameter value. The drawback of get_any
is that application code must use a select-type construct in order to use
the returned value, making it more complex to use. If present, the optional
argument default
must have the same rank as value
. If the named
parameter does not exist, it is created with the value prescribed by
default
, and that value is returned in value
. It is an error if
the named parameter does not exist and default
is not present. It is
an error if the named parameter is a sublist. It is an error if the rank
of value
does not match that of the stored value of the named
parameter.
set_name(name)
Set the name of the parameter list to name
. A parameter list
created by sublist
is automatically assigned a default name. It is
the name of the parent parameter list appended with “->
” followed
by the sublist parameter name. Use this function to override the default
name.
sublist(name [,stat [,errmsg]])
Returns a type(parameter_list)
pointer to the named parameter
sublist. The parameter is created with an empty sublist value if it
does not already exist. It is an error if the parameter exists but is
not a sublist.
is_parameter(name)
returns true if there is a parameter with the given name
;
otherwise false.
is_sublist(name)
Returns true if there is a sublist parameter with the given name
;
otherwise false.
is_scalar(name)
Returns true if there is a scalar-valued parameter with the given name
;
otherwise false.
is_vector(name)
Returns true if there is a vector-valued parameter with the given name
;
otherwise false.
is_matrix(name)
Returns true if there is a matrix-valued parameter with the given name
;
otherwise false.
count()
Returns the number of parameters stored in the parameter list.
name()
Returns the name of the parameter list. If no name was assigned to the
parameter list, the name ‘$
’ is returned. Be careful not to confuse
the name of a sublist parameter with the name of the parameter list that
is its value; they are not the same thing.
Caution
Derived type values with pointer components, direct or indirect, should be
used advisedly. The map_any derived type is used to
store the parameter name/value pairs, and the values are sourced-allocation
copies of the values passed to the set
method. This makes a shallow
copy of any pointer component. The original pointer and its copy will have
the same target; no copy of the target is made. This also applies to
parameter list assignment, where values in the lhs are sourced-allocation
copies of the rhs.
Parameter values can be accessed in a parameter list directly, but only if
the parameter names are known. The derived type parameter_list_iterator
provides a means of iterating through the parameters in a parameter_list
object, sequentially visiting each parameter in the list once and only once.
A defined parameter_list_iterator
object is positioned at a particular
parameter of its associated parameter list, or at a pseudo-position the-end,
and can be queried for the name and value of that parameter. Scalar assignment
is defined for parameter_list_iterator
objects. The lhs iterator becomes
associated with the same parameter list as the rhs iterator and is positioned
at the same parameter. Subsequent changes to one iterator do not affect the
other. An iterator object is normally defined by assignment from a structure
constructor expression; see below.
parameter_list_iterator(plist [,sublists_only])
Returns an iterator positioned at the initial parameter of the parameter
list``plist``, or the-end if the parameter list is empty. If the optional
logical argument sublists_only
is present with value true, parameters
other than sublists are skipped by the iterator.
Constructor expressions are used to initialize iterator objects:
type(parameter_list) :: plist
type(parameter_list_iterator) :: iter
iter = parameter_list_iterator(plist)
next()
Advances the iterator to the next parameter in the list, or to the-end if there are no more parameters remaining to be visited. This call has no effect if the iterator is already positioned at the-end.
at_end()
Returns true if the iterator is positioned at the-end; otherwise false.
name()
Returns the name of the current parameter. The iterator must not be positioned at the-end.
is_list()
Returns true if the current parameter value is a sublist; otherwise false. The iterator must not be positioned at the-end.
is_scalar()
Returns true if the current parameter has a scalar value; otherwise false. The iterator must not be positioned at the-end.
is_vector()
Returns true if the current parameter has a rank-1 array value; otherwise false. The iterator must not be positioned at the-end.
is_matrix()
Returns true if the current parameter has a rank-2 array value; otherwise false. The iterator must not be positioned at the-end.
sublist()
Returns a parameter_list
pointer associated with the current parameter
value if it is a sublist; otherwise it returns a null()
pointer.
scalar()
Returns a class(*)
pointer to the current parameter value if it is a
scalar value; otherwise it returns a null()
pointer.
vector()
Returns a class(*)
rank-1 array pointer to the current parameter value
if it is a vector value; otherwise it returns a null()
pointer.
matrix()
Returns a class(*)
rank-2 array pointer to the current parameter value
if it is a matrix value; otherwise it returns a null()
pointer.
count()
Returns the number of remaining parameters, including the current one.
Parameter list values are stored internally in objects of class
parameter_entry
. There are four different concrete extensions of this
abstract type: any_scalar
, which stores a scalar value of any intrinsic
or derived type; any_vector
, which stores a rank-1 array value of any
intrinsic or derived type; any_matrix
, which stores a rank-2 array value
of any intrinsic or derived type; and parameter_list
itself. This
internal implementation detail can mostly be ignored; all of the procedures
described so far hide this detail, for example. The following procedure is
the exception.
entry()
Returns a class(parameter_entry)
pointer to an object that holds
the value of the current parameter. The iterator must not be positioned
at the-end. A select-type construct with stanzas for each of the four
possible dynamic types is required to access the value. It is generally
easier to use the preceding functions instead. For example, with sublists
it is easier to use the is_list
method to identify whether the current
parameter is a sublist, and if so use the sublist
method to acess the
sublist.
JSON is a widely-used data interchange format (http://www.json.org). A parameter list whose primitive values are of intrinsic types (integer, real, character, logical) can be represented quite naturally as JSON text that conforms to a subset of the JSON format:
A parameter list is represented by a JSON object, which is an unordered
list of comma-separated name : value pairs enclosed in braces
({
and }
).
A parameter name and value are represented by a name : value pair of the object:
A name is a string enclosed in double quotes.
A value may be a string (in double quotes), an integer, a real
number, or a boolean (the tokens true
or false
).
A value may be also be a JSON array, which is an ordered list of
comma-separated values enclosed in brackets ([
and ]
). To
represent an array parameter value, the values in a JSON array are
restricted to scalars of the same primitive type or such JSON arrays
themselves. Nesting, however, is limited to 1 level (rank-2 arrays)
and the sub-arrays must all have the same length. The values are listed
in Fortran array element order. Simply stated, JSON arrays are limited to
things that exactly correspond to a rank-1 or 2 Fortran array of intrinsic
type. JSON generally allows jagged arrays of any JSON values, possibly of
differing types.
A value may also be a JSON object that represents a parameter sublist.
Null values (the token null
) are not allowed.
0-sized arrays are not allowed.
Comments (text starting from //
to the end of the line) are allowed;
this is an extention to the JSON standard that is allowed by the YAJL
library that performs the actual parsing of the JSON text.
The parameter_list_json
module provides the following procedures for
creating a parameter list object from JSON text and for producing a JSON
text representation of a parameter list object.
Note
When reading JSON text, booleans are converted to logical values of default
kind, integer numbers converted to integer values of default kind, and real
numbers converted to real(real64)
values. For numbers, this reflects
the behavior of the YAJL parser. A future enhancement could allow for these
values to be converted to user-specified kinds before being added to the
parameter list.
call parameter_list_from_json_stream(unit, plist, errmsg)
Reads JSON text from the given logical unit
, which must be connected for
unformatted stream access, and creates the corresponding parameter list, as
described above. The intent-out type(parameter_list)
pointer argument
plist
returns the created parameter list. An unassociated return value
indicates an error condition, in which case the allocatable deferred-length
character argument errmsg
is assigned an explanatory error message.
call parameter_list_from_json_stream(unit, name, plist, errmsg)
Does exactly the same thing as the preceding subroutine except that plist
is assigned the given name
instead of the default “$”. Although the
name can be reset after the fact, this would not be reflected in the names
of any sublists created by the stream, whose names are automatically
generated from the name of their parent parameter lists.
call parameter_list_from_json_string(string, plist, errmsg)
Does exactly the same thing as parameter_list_from_json_stream
except
that the JSON text is read from the character variable string
.
call parameters_from json_stream(unit, plist, stat, errmsg)
This differs from parameter_list_from_json_stream
in that plist
is an intent-inout variable and the parameters read from the stream are
added to plist
. In the event of an error, the integer stat
returns a non-zero value, and the allocatable deferred-length character
argument errmsg
is assigned an explanatory error message.
call parameters_from_json_string(string, plist, stat, errmsg)
Does exactly the same thing as parameters_from_json_stream
except that
the JSON text is read from the character variable string
.
call parameter_list_to_json(plist, unit)
Writes the JSON text representation of the parameter list plist
to unit
, which must be connected for formatted write access.
The parameter list values other than sublists must be of intrinsic primitive
types that are representable in JSON: logical, integer, real, character.