LCOV - code coverage report
Current view: top level - src/api - hsd_access.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 61.5 % 384 236
Test Date: 2026-03-19 13:06:14 Functions: 72.5 % 40 29

            Line data    Source code
       1              : !> HSD Access Object
       2              : !>
       3              : !> Provides a configurable, error-accumulating interface for reading and writing
       4              : !> HSD tree values. This is the primary user-facing API for the HSD library.
       5              : !>
       6              : !> ## Usage
       7              : !>
       8              : !> ```fortran
       9              : !> type(hsd_node_t), target :: root
      10              : !> type(hsd_access_t) :: access
      11              : !>
      12              : !> call hsd_load_file("input.hsd", root, error)
      13              : !> call access%init(root)
      14              : !> call access%get("Geometry/Periodic", periodic, default=.false.)
      15              : !> call access%get("Hamiltonian/MaxIter", max_iter, default=100)
      16              : !> if (access%has_errors()) then
      17              : !>   call access%print_errors()
      18              : !>   stop 1
      19              : !> end if
      20              : !> ```
      21              : !>
      22              : !> ## On-Missing Behavior
      23              : !>
      24              : !> When a key is not found and a default is provided, the `on_missing` setting
      25              : !> controls what happens:
      26              : !>
      27              : !> - `HSD_ON_MISSING_SET` (default): writes the default value back to the tree,
      28              : !>   critical for generating processed output (e.g. dftb_pin.hsd).
      29              : !> - `HSD_ON_MISSING_RETURN`: returns the default without modifying the tree.
      30              : !>
      31              : !> ## Thread Safety
      32              : !>
      33              : !> The access object is NOT thread-safe. Each thread should use its own instance.
      34              : !> The root node must have the `target` attribute and must outlive the access object.
      35              : module hsd_access
      36              :   use, intrinsic :: iso_fortran_env, only: error_unit
      37            2 :   use hsd_constants, only: dp
      38              :   use hsd_utils, only: to_lower
      39              :   use hsd_error, only: HSD_STAT_OK, HSD_STAT_NOT_FOUND, HSD_STAT_TYPE_ERROR
      40              :   use hsd_types, only: hsd_node_t, NODE_TYPE_TABLE, NODE_TYPE_VALUE
      41              :   use hsd_api, only: hsd_get_child, hsd_set, hsd_get_matrix
      42              :   implicit none (type, external)
      43              :   private
      44              : 
      45              :   !> Write default value back to tree when key is missing
      46              :   integer, parameter, public :: HSD_ON_MISSING_SET = 1
      47              :   !> Return default without modifying the tree
      48              :   integer, parameter, public :: HSD_ON_MISSING_RETURN = 2
      49              : 
      50              :   !> Single error entry in the error stack
      51              :   type, public :: hsd_error_entry_t
      52              :     character(len=:), allocatable :: path
      53              :     integer :: stat = 0
      54              :     character(len=:), allocatable :: message
      55              :   end type hsd_error_entry_t
      56              : 
      57              :   !> Configurable access object for HSD trees
      58              :   !>
      59              :   !> Wraps an HSD tree root with configurable behavior (processed marking,
      60              :   !> default handling) and accumulates errors for batch checking.
      61              :   type, public :: hsd_access_t
      62              :     !> Non-owning pointer to tree root (must have target attribute)
      63              :     type(hsd_node_t), pointer :: root => null()
      64              :     !> Whether to set the processed flag on accessed nodes
      65              :     logical :: mark_processed = .true.
      66              :     !> Behavior when a key is not found and a default is provided
      67              :     integer :: on_missing = HSD_ON_MISSING_SET
      68              :     !> Error stack (grows by doubling)
      69              :     type(hsd_error_entry_t), allocatable :: errors(:)
      70              :     !> Number of errors currently in the stack
      71              :     integer :: num_errors = 0
      72              :   contains
      73              :     procedure :: init => access_init
      74              :     ! Scalar getters
      75              :     procedure, private :: get_string => access_get_string
      76              :     procedure, private :: get_integer => access_get_integer
      77              :     procedure, private :: get_real_dp => access_get_real_dp
      78              :     procedure, private :: get_logical => access_get_logical
      79              :     procedure, private :: get_complex_dp => access_get_complex_dp
      80              :     ! Array getters
      81              :     procedure, private :: get_integer_array => access_get_integer_array
      82              :     procedure, private :: get_real_dp_array => access_get_real_dp_array
      83              :     procedure, private :: get_logical_array => access_get_logical_array
      84              :     procedure, private :: get_string_array => access_get_string_array
      85              :     procedure, private :: get_complex_dp_array => access_get_complex_dp_array
      86              :     !> Generic value getter (resolves on val type and rank)
      87              :     generic :: get => get_string, get_integer, get_real_dp, get_logical, &
      88              :         & get_complex_dp, get_integer_array, get_real_dp_array, &
      89              :         & get_logical_array, get_string_array, get_complex_dp_array
      90              :     ! Matrix getters
      91              :     procedure, private :: get_int_matrix => access_get_int_matrix
      92              :     procedure, private :: get_real_matrix => access_get_real_matrix
      93              :     procedure, private :: get_complex_matrix => access_get_complex_matrix
      94              :     !> Generic matrix getter (resolves on val element type)
      95              :     generic :: get_matrix => get_int_matrix, get_real_matrix, get_complex_matrix
      96              :     ! Scalar setters
      97              :     procedure, private :: set_string => access_set_string
      98              :     procedure, private :: set_integer => access_set_integer
      99              :     procedure, private :: set_real_dp => access_set_real_dp
     100              :     procedure, private :: set_logical => access_set_logical
     101              :     procedure, private :: set_complex_dp => access_set_complex_dp
     102              :     ! Array setters
     103              :     procedure, private :: set_integer_array => access_set_integer_array
     104              :     procedure, private :: set_real_dp_array => access_set_real_dp_array
     105              :     procedure, private :: set_logical_array => access_set_logical_array
     106              :     procedure, private :: set_string_array => access_set_string_array
     107              :     procedure, private :: set_complex_dp_array => access_set_complex_dp_array
     108              :     ! Matrix setters
     109              :     procedure, private :: set_integer_matrix => access_set_integer_matrix
     110              :     procedure, private :: set_real_dp_matrix => access_set_real_dp_matrix
     111              :     procedure, private :: set_complex_dp_matrix => access_set_complex_dp_matrix
     112              :     !> Generic value/array/matrix setter (resolves on val type and rank)
     113              :     generic :: set => set_string, set_integer, set_real_dp, set_logical, &
     114              :         & set_complex_dp, set_integer_array, set_real_dp_array, &
     115              :         & set_logical_array, set_string_array, set_complex_dp_array, &
     116              :         & set_integer_matrix, set_real_dp_matrix, set_complex_dp_matrix
     117              :     ! Choice getter
     118              :     procedure :: get_choice => access_get_choice
     119              :     ! Error handling
     120              :     procedure :: has_errors => access_has_errors
     121              :     procedure :: error_count => access_error_count
     122              :     procedure :: print_errors => access_print_errors
     123              :     procedure :: clear_errors => access_clear_errors
     124              :     procedure :: get_errors => access_get_errors
     125              :     ! Internal helpers
     126              :     procedure, private :: push_error => access_push_error
     127              :     procedure, private :: resolve_value_node => access_resolve_value_node
     128              :     procedure, private :: mark_path_processed => access_mark_path_processed
     129              :   end type hsd_access_t
     130              : 
     131              : contains
     132              : 
     133              :   ! ===== Initialization =====
     134              : 
     135              :   !> Initialize the access object with a tree root
     136           78 :   subroutine access_init(self, root, mark_processed, on_missing)
     137              :     class(hsd_access_t), intent(out) :: self
     138              :     type(hsd_node_t), intent(in), target :: root
     139              :     logical, intent(in), optional :: mark_processed
     140              :     integer, intent(in), optional :: on_missing
     141              : 
     142           39 :     self%root => root
     143           39 :     if (present(mark_processed)) self%mark_processed = mark_processed
     144           39 :     if (present(on_missing)) self%on_missing = on_missing
     145          663 :     allocate(self%errors(16))
     146           39 :     self%num_errors = 0
     147           39 :   end subroutine access_init
     148              : 
     149              :   ! ===== Error Stack =====
     150              : 
     151              :   !> Push an error entry onto the stack
     152            9 :   subroutine access_push_error(self, path, stat, message)
     153              :     class(hsd_access_t), intent(inout) :: self
     154              :     character(len=*), intent(in) :: path
     155              :     integer, intent(in) :: stat
     156              :     character(len=*), intent(in) :: message
     157              : 
     158            9 :     type(hsd_error_entry_t), allocatable :: tmp(:)
     159              : 
     160            0 :     if (.not. allocated(self%errors)) allocate(self%errors(16))
     161              : 
     162            9 :     if (self%num_errors >= size(self%errors)) then
     163            0 :       allocate(tmp(size(self%errors) * 2))
     164            0 :       tmp(1:self%num_errors) = self%errors(1:self%num_errors)
     165            0 :       call move_alloc(tmp, self%errors)
     166              :     end if
     167              : 
     168            9 :     self%num_errors = self%num_errors + 1
     169            9 :     self%errors(self%num_errors)%path = path
     170            9 :     self%errors(self%num_errors)%stat = stat
     171            9 :     self%errors(self%num_errors)%message = message
     172           48 :   end subroutine access_push_error
     173              : 
     174              :   !> Check whether any errors have been accumulated
     175           32 :   pure function access_has_errors(self) result(has)
     176              :     class(hsd_access_t), intent(in) :: self
     177              :     logical :: has
     178           32 :     has = self%num_errors > 0
     179            9 :   end function access_has_errors
     180              : 
     181              :   !> Return the number of accumulated errors
     182            3 :   pure function access_error_count(self) result(n)
     183              :     class(hsd_access_t), intent(in) :: self
     184              :     integer :: n
     185            3 :     n = self%num_errors
     186           32 :   end function access_error_count
     187              : 
     188              :   !> Print all accumulated errors to a file unit (default: stderr)
     189            1 :   subroutine access_print_errors(self, unit)
     190              :     class(hsd_access_t), intent(in) :: self
     191              :     integer, intent(in), optional :: unit
     192              : 
     193            1 :     integer :: iu, ii
     194              : 
     195            1 :     iu = error_unit
     196            1 :     if (present(unit)) iu = unit
     197              : 
     198            2 :     do ii = 1, self%num_errors
     199              :       write(iu, '(A,A,A,A)') &
     200            1 :           & "Error at '", self%errors(ii)%path, &
     201            3 :           & "': ", self%errors(ii)%message
     202              :     end do
     203            3 :   end subroutine access_print_errors
     204              : 
     205              :   !> Clear all accumulated errors
     206            1 :   subroutine access_clear_errors(self)
     207              :     class(hsd_access_t), intent(inout) :: self
     208            1 :     self%num_errors = 0
     209            1 :   end subroutine access_clear_errors
     210              : 
     211              :   !> Return a copy of all accumulated errors
     212            1 :   subroutine access_get_errors(self, errors)
     213              :     class(hsd_access_t), intent(in) :: self
     214              :     type(hsd_error_entry_t), allocatable, intent(out) :: errors(:)
     215              : 
     216            1 :     if (self%num_errors > 0) then
     217            4 :       errors = self%errors(1:self%num_errors)
     218              :     else
     219            0 :       allocate(errors(0))
     220              :     end if
     221            2 :   end subroutine access_get_errors
     222              : 
     223              :   ! ===== Internal Helpers =====
     224              : 
     225              :   !> Resolve a path to a value node, handling inline text transparently
     226           35 :   subroutine access_resolve_value_node(self, path, val_node, stat)
     227              :     class(hsd_access_t), intent(inout) :: self
     228              :     character(len=*), intent(in) :: path
     229              :     type(hsd_node_t), pointer, intent(out) :: val_node
     230              :     integer, intent(out) :: stat
     231              : 
     232              :     type(hsd_node_t), pointer :: child
     233              : 
     234           35 :     nullify(val_node)
     235           35 :     call hsd_get_child(self%root, path, child, stat)
     236           35 :     if (stat /= HSD_STAT_OK .or. .not. associated(child)) then
     237           14 :       stat = HSD_STAT_NOT_FOUND
     238           14 :       return
     239              :     end if
     240              : 
     241           21 :     if (self%mark_processed) child%processed = .true.
     242              : 
     243           21 :     if (child%node_type == NODE_TYPE_VALUE) then
     244           21 :       val_node => child
     245           21 :       stat = HSD_STAT_OK
     246            0 :     else if (child%node_type == NODE_TYPE_TABLE) then
     247              :       ! Try to extract inline value (#text child)
     248            0 :       call child%get_child_by_name("#text", val_node)
     249            0 :       if (associated(val_node) .and. &
     250              :           & val_node%node_type == NODE_TYPE_VALUE) then
     251            0 :         stat = HSD_STAT_OK
     252              :       else
     253            0 :         nullify(val_node)
     254            0 :         stat = HSD_STAT_TYPE_ERROR
     255              :       end if
     256              :     else
     257            0 :       stat = HSD_STAT_TYPE_ERROR
     258              :     end if
     259           36 :   end subroutine access_resolve_value_node
     260              : 
     261              :   !> Mark the child at the given path as processed
     262            6 :   subroutine access_mark_path_processed(self, path)
     263              :     class(hsd_access_t), intent(inout) :: self
     264              :     character(len=*), intent(in) :: path
     265              : 
     266              :     type(hsd_node_t), pointer :: child
     267            6 :     integer :: local_stat
     268              : 
     269            0 :     if (.not. self%mark_processed) return
     270            6 :     call hsd_get_child(self%root, path, child, local_stat)
     271            6 :     if (local_stat == HSD_STAT_OK .and. associated(child)) &
     272            6 :         & child%processed = .true.
     273           41 :   end subroutine access_mark_path_processed
     274              : 
     275              :   ! ===== Scalar Getters =====
     276              : 
     277              :   !> Get string value by path
     278            4 :   subroutine access_get_string(self, path, val, default)
     279              :     class(hsd_access_t), intent(inout) :: self
     280              :     character(len=*), intent(in) :: path
     281              :     character(len=:), allocatable, intent(out) :: val
     282              :     character(len=*), intent(in), optional :: default
     283              : 
     284              :     type(hsd_node_t), pointer :: vnode
     285            4 :     integer :: local_stat
     286              : 
     287            4 :     call self%resolve_value_node(path, vnode, local_stat)
     288            4 :     if (local_stat == HSD_STAT_OK) then
     289            2 :       call vnode%get_string(val, local_stat)
     290            2 :       if (local_stat /= HSD_STAT_OK) then
     291              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     292            0 :             & "Cannot read as string")
     293            0 :         val = ""
     294              :       end if
     295            2 :       return
     296              :     end if
     297              : 
     298            2 :     if (present(default)) then
     299            1 :       val = default
     300            1 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     301            1 :         call hsd_set(self%root, path, default)
     302            1 :         call self%mark_path_processed(path)
     303              :       end if
     304              :     else
     305              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     306            1 :           & "Required string field not found")
     307            1 :       val = ""
     308              :     end if
     309           10 :   end subroutine access_get_string
     310              : 
     311              :   !> Get integer value by path
     312           15 :   subroutine access_get_integer(self, path, val, default)
     313              :     class(hsd_access_t), intent(inout) :: self
     314              :     character(len=*), intent(in) :: path
     315              :     integer, intent(out) :: val
     316              :     integer, intent(in), optional :: default
     317              : 
     318              :     type(hsd_node_t), pointer :: vnode
     319           15 :     integer :: local_stat
     320              : 
     321           15 :     call self%resolve_value_node(path, vnode, local_stat)
     322           15 :     if (local_stat == HSD_STAT_OK) then
     323            7 :       call vnode%get_integer(val, local_stat)
     324            7 :       if (local_stat /= HSD_STAT_OK) then
     325              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     326            1 :             & "Cannot parse as integer")
     327            1 :         val = 0
     328              :       end if
     329            7 :       return
     330              :     end if
     331              : 
     332            8 :     if (present(default)) then
     333            3 :       val = default
     334            3 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     335            2 :         call hsd_set(self%root, path, default)
     336            2 :         call self%mark_path_processed(path)
     337              :       end if
     338              :     else
     339              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     340            5 :           & "Required integer field not found")
     341            5 :       val = 0
     342              :     end if
     343           19 :   end subroutine access_get_integer
     344              : 
     345              :   !> Get double precision real value by path
     346            4 :   subroutine access_get_real_dp(self, path, val, default)
     347              :     class(hsd_access_t), intent(inout) :: self
     348              :     character(len=*), intent(in) :: path
     349              :     real(dp), intent(out) :: val
     350              :     real(dp), intent(in), optional :: default
     351              : 
     352              :     type(hsd_node_t), pointer :: vnode
     353            4 :     integer :: local_stat
     354              : 
     355            4 :     call self%resolve_value_node(path, vnode, local_stat)
     356            4 :     if (local_stat == HSD_STAT_OK) then
     357            2 :       call vnode%get_real(val, local_stat)
     358            2 :       if (local_stat /= HSD_STAT_OK) then
     359              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     360            0 :             & "Cannot parse as real")
     361            0 :         val = 0.0_dp
     362              :       end if
     363            2 :       return
     364              :     end if
     365              : 
     366            2 :     if (present(default)) then
     367            1 :       val = default
     368            1 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     369            1 :         call hsd_set(self%root, path, default)
     370            1 :         call self%mark_path_processed(path)
     371              :       end if
     372              :     else
     373              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     374            1 :           & "Required real field not found")
     375            1 :       val = 0.0_dp
     376              :     end if
     377           19 :   end subroutine access_get_real_dp
     378              : 
     379              :   !> Get logical value by path
     380            3 :   subroutine access_get_logical(self, path, val, default)
     381              :     class(hsd_access_t), intent(inout) :: self
     382              :     character(len=*), intent(in) :: path
     383              :     logical, intent(out) :: val
     384              :     logical, intent(in), optional :: default
     385              : 
     386              :     type(hsd_node_t), pointer :: vnode
     387            3 :     integer :: local_stat
     388              : 
     389            3 :     call self%resolve_value_node(path, vnode, local_stat)
     390            3 :     if (local_stat == HSD_STAT_OK) then
     391            2 :       call vnode%get_logical(val, local_stat)
     392            2 :       if (local_stat /= HSD_STAT_OK) then
     393              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     394            0 :             & "Cannot parse as logical")
     395            0 :         val = .false.
     396              :       end if
     397            2 :       return
     398              :     end if
     399              : 
     400            1 :     if (present(default)) then
     401            1 :       val = default
     402            1 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     403            1 :         call hsd_set(self%root, path, default)
     404            1 :         call self%mark_path_processed(path)
     405              :       end if
     406              :     else
     407              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     408            0 :           & "Required logical field not found")
     409            0 :       val = .false.
     410              :     end if
     411            7 :   end subroutine access_get_logical
     412              : 
     413              :   !> Get complex double precision value by path
     414            1 :   subroutine access_get_complex_dp(self, path, val, default)
     415              :     class(hsd_access_t), intent(inout) :: self
     416              :     character(len=*), intent(in) :: path
     417              :     complex(dp), intent(out) :: val
     418              :     complex(dp), intent(in), optional :: default
     419              : 
     420              :     type(hsd_node_t), pointer :: vnode
     421            1 :     integer :: local_stat
     422              : 
     423            1 :     call self%resolve_value_node(path, vnode, local_stat)
     424            1 :     if (local_stat == HSD_STAT_OK) then
     425            1 :       call vnode%get_complex(val, local_stat)
     426            1 :       if (local_stat /= HSD_STAT_OK) then
     427              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     428            0 :             & "Cannot parse as complex")
     429            0 :         val = (0.0_dp, 0.0_dp)
     430              :       end if
     431            1 :       return
     432              :     end if
     433              : 
     434            0 :     if (present(default)) then
     435            0 :       val = default
     436            0 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     437            0 :         call hsd_set(self%root, path, default)
     438            0 :         call self%mark_path_processed(path)
     439              :       end if
     440              :     else
     441              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     442            0 :           & "Required complex field not found")
     443            0 :       val = (0.0_dp, 0.0_dp)
     444              :     end if
     445            4 :   end subroutine access_get_complex_dp
     446              : 
     447              :   ! ===== Array Getters =====
     448              : 
     449              :   !> Get integer array by path
     450            6 :   subroutine access_get_integer_array(self, path, val, default)
     451              :     class(hsd_access_t), intent(inout) :: self
     452              :     character(len=*), intent(in) :: path
     453              :     integer, allocatable, intent(out) :: val(:)
     454              :     integer, intent(in), optional :: default(:)
     455              : 
     456              :     type(hsd_node_t), pointer :: vnode
     457            3 :     integer :: local_stat
     458              : 
     459            3 :     call self%resolve_value_node(path, vnode, local_stat)
     460            3 :     if (local_stat == HSD_STAT_OK) then
     461            2 :       call vnode%get_int_array(val, local_stat)
     462            2 :       if (local_stat /= HSD_STAT_OK) then
     463              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     464            0 :             & "Cannot parse as integer array")
     465            0 :         allocate(val(0))
     466              :       end if
     467            2 :       return
     468              :     end if
     469              : 
     470            1 :     if (present(default)) then
     471            4 :       val = default
     472            1 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     473            1 :         call hsd_set(self%root, path, default)
     474            1 :         call self%mark_path_processed(path)
     475              :       end if
     476              :     else
     477              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     478            0 :           & "Required integer array field not found")
     479            0 :       allocate(val(0))
     480              :     end if
     481            4 :   end subroutine access_get_integer_array
     482              : 
     483              :   !> Get double precision real array by path
     484            4 :   subroutine access_get_real_dp_array(self, path, val, default)
     485              :     class(hsd_access_t), intent(inout) :: self
     486              :     character(len=*), intent(in) :: path
     487              :     real(dp), allocatable, intent(out) :: val(:)
     488              :     real(dp), intent(in), optional :: default(:)
     489              : 
     490              :     type(hsd_node_t), pointer :: vnode
     491            2 :     integer :: local_stat
     492              : 
     493            2 :     call self%resolve_value_node(path, vnode, local_stat)
     494            2 :     if (local_stat == HSD_STAT_OK) then
     495            2 :       call vnode%get_real_array(val, local_stat)
     496            2 :       if (local_stat /= HSD_STAT_OK) then
     497              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     498            0 :             & "Cannot parse as real array")
     499            0 :         allocate(val(0))
     500              :       end if
     501            2 :       return
     502              :     end if
     503              : 
     504            0 :     if (present(default)) then
     505            0 :       val = default
     506            0 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     507            0 :         call hsd_set(self%root, path, default)
     508            0 :         call self%mark_path_processed(path)
     509              :       end if
     510              :     else
     511              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     512            0 :           & "Required real array field not found")
     513            0 :       allocate(val(0))
     514              :     end if
     515            5 :   end subroutine access_get_real_dp_array
     516              : 
     517              :   !> Get logical array by path
     518            2 :   subroutine access_get_logical_array(self, path, val, default)
     519              :     class(hsd_access_t), intent(inout) :: self
     520              :     character(len=*), intent(in) :: path
     521              :     logical, allocatable, intent(out) :: val(:)
     522              :     logical, intent(in), optional :: default(:)
     523              : 
     524              :     type(hsd_node_t), pointer :: vnode
     525            1 :     integer :: local_stat
     526              : 
     527            1 :     call self%resolve_value_node(path, vnode, local_stat)
     528            1 :     if (local_stat == HSD_STAT_OK) then
     529            1 :       call vnode%get_logical_array(val, local_stat)
     530            1 :       if (local_stat /= HSD_STAT_OK) then
     531              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     532            0 :             & "Cannot parse as logical array")
     533            0 :         allocate(val(0))
     534              :       end if
     535            1 :       return
     536              :     end if
     537              : 
     538            0 :     if (present(default)) then
     539            0 :       val = default
     540            0 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     541            0 :         call hsd_set(self%root, path, default)
     542            0 :         call self%mark_path_processed(path)
     543              :       end if
     544              :     else
     545              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     546            0 :           & "Required logical array field not found")
     547            0 :       allocate(val(0))
     548              :     end if
     549            3 :   end subroutine access_get_logical_array
     550              : 
     551              :   !> Get string array by path
     552            2 :   subroutine access_get_string_array(self, path, val, default)
     553              :     class(hsd_access_t), intent(inout) :: self
     554              :     character(len=*), intent(in) :: path
     555              :     character(len=:), allocatable, intent(out) :: val(:)
     556              :     character(len=*), intent(in), optional :: default(:)
     557              : 
     558              :     type(hsd_node_t), pointer :: vnode
     559            1 :     integer :: local_stat
     560              : 
     561            1 :     call self%resolve_value_node(path, vnode, local_stat)
     562            1 :     if (local_stat == HSD_STAT_OK) then
     563            1 :       call vnode%get_string_array(val, local_stat)
     564            1 :       if (local_stat /= HSD_STAT_OK) then
     565              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     566            0 :             & "Cannot parse as string array")
     567            0 :         allocate(character(len=0) :: val(0))
     568              :       end if
     569            1 :       return
     570              :     end if
     571              : 
     572            0 :     if (present(default)) then
     573            0 :       val = default
     574            0 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     575            0 :         call hsd_set(self%root, path, default)
     576            0 :         call self%mark_path_processed(path)
     577              :       end if
     578              :     else
     579              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     580            0 :           & "Required string array field not found")
     581            0 :       allocate(character(len=0) :: val(0))
     582              :     end if
     583            2 :   end subroutine access_get_string_array
     584              : 
     585              :   !> Get complex double precision array by path
     586            2 :   subroutine access_get_complex_dp_array(self, path, val, default)
     587              :     class(hsd_access_t), intent(inout) :: self
     588              :     character(len=*), intent(in) :: path
     589              :     complex(dp), allocatable, intent(out) :: val(:)
     590              :     complex(dp), intent(in), optional :: default(:)
     591              : 
     592              :     type(hsd_node_t), pointer :: vnode
     593            1 :     integer :: local_stat
     594              : 
     595            1 :     call self%resolve_value_node(path, vnode, local_stat)
     596            1 :     if (local_stat == HSD_STAT_OK) then
     597            1 :       call vnode%get_complex_array(val, local_stat)
     598            1 :       if (local_stat /= HSD_STAT_OK) then
     599              :         call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     600            0 :             & "Cannot parse as complex array")
     601            0 :         allocate(val(0))
     602              :       end if
     603            1 :       return
     604              :     end if
     605              : 
     606            0 :     if (present(default)) then
     607            0 :       val = default
     608            0 :       if (self%on_missing == HSD_ON_MISSING_SET) then
     609            0 :         call hsd_set(self%root, path, default)
     610            0 :         call self%mark_path_processed(path)
     611              :       end if
     612              :     else
     613              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     614            0 :           & "Required complex array field not found")
     615            0 :       allocate(val(0))
     616              :     end if
     617            2 :   end subroutine access_get_complex_dp_array
     618              : 
     619              :   ! ===== Matrix Getters =====
     620              : 
     621              :   !> Get integer matrix by path
     622            0 :   subroutine access_get_int_matrix(self, path, val, nrows, ncols, order)
     623              :     class(hsd_access_t), intent(inout) :: self
     624              :     character(len=*), intent(in) :: path
     625              :     integer, allocatable, intent(out) :: val(:,:)
     626              :     integer, intent(out) :: nrows, ncols
     627              :     character(len=*), intent(in), optional :: order
     628              : 
     629              :     type(hsd_node_t), pointer :: child
     630            0 :     integer :: local_stat
     631            0 :     logical :: was_processed
     632              : 
     633              :     ! Save processed state to restore if mark_processed is off
     634            0 :     was_processed = .false.
     635            0 :     call hsd_get_child(self%root, path, child, local_stat)
     636            0 :     if (local_stat == HSD_STAT_OK .and. associated(child)) &
     637            0 :         & was_processed = child%processed
     638              : 
     639            0 :     call hsd_get_matrix(self%root, path, val, nrows, ncols, &
     640            0 :         & local_stat, order)
     641              : 
     642            0 :     if (local_stat /= HSD_STAT_OK) then
     643              :       call self%push_error(path, local_stat, &
     644            0 :           & "Cannot read integer matrix")
     645            0 :       return
     646              :     end if
     647              : 
     648            0 :     if (.not. self%mark_processed .and. associated(child)) &
     649            0 :         & child%processed = was_processed
     650            1 :   end subroutine access_get_int_matrix
     651              : 
     652              :   !> Get double precision real matrix by path
     653            2 :   subroutine access_get_real_matrix(self, path, val, nrows, ncols, order)
     654              :     class(hsd_access_t), intent(inout) :: self
     655              :     character(len=*), intent(in) :: path
     656              :     real(dp), allocatable, intent(out) :: val(:,:)
     657              :     integer, intent(out) :: nrows, ncols
     658              :     character(len=*), intent(in), optional :: order
     659              : 
     660              :     type(hsd_node_t), pointer :: child
     661            2 :     integer :: local_stat
     662            2 :     logical :: was_processed
     663              : 
     664            2 :     was_processed = .false.
     665            2 :     call hsd_get_child(self%root, path, child, local_stat)
     666            2 :     if (local_stat == HSD_STAT_OK .and. associated(child)) &
     667            2 :         & was_processed = child%processed
     668              : 
     669            0 :     call hsd_get_matrix(self%root, path, val, nrows, ncols, &
     670            2 :         & local_stat, order)
     671              : 
     672            2 :     if (local_stat /= HSD_STAT_OK) then
     673              :       call self%push_error(path, local_stat, &
     674            0 :           & "Cannot read real matrix")
     675            0 :       return
     676              :     end if
     677              : 
     678            2 :     if (.not. self%mark_processed .and. associated(child)) &
     679            0 :         & child%processed = was_processed
     680            2 :   end subroutine access_get_real_matrix
     681              : 
     682              :   !> Get complex double precision matrix by path
     683            0 :   subroutine access_get_complex_matrix(self, path, val, nrows, ncols, &
     684              :       & order)
     685              :     class(hsd_access_t), intent(inout) :: self
     686              :     character(len=*), intent(in) :: path
     687              :     complex(dp), allocatable, intent(out) :: val(:,:)
     688              :     integer, intent(out) :: nrows, ncols
     689              :     character(len=*), intent(in), optional :: order
     690              : 
     691              :     type(hsd_node_t), pointer :: child
     692            0 :     integer :: local_stat
     693            0 :     logical :: was_processed
     694              : 
     695            0 :     was_processed = .false.
     696            0 :     call hsd_get_child(self%root, path, child, local_stat)
     697            0 :     if (local_stat == HSD_STAT_OK .and. associated(child)) &
     698            0 :         & was_processed = child%processed
     699              : 
     700            0 :     call hsd_get_matrix(self%root, path, val, nrows, ncols, &
     701            0 :         & local_stat, order)
     702              : 
     703            0 :     if (local_stat /= HSD_STAT_OK) then
     704              :       call self%push_error(path, local_stat, &
     705            0 :           & "Cannot read complex matrix")
     706            0 :       return
     707              :     end if
     708              : 
     709            0 :     if (.not. self%mark_processed .and. associated(child)) &
     710            0 :         & child%processed = was_processed
     711            2 :   end subroutine access_get_complex_matrix
     712              : 
     713              :   ! ===== Scalar Setters =====
     714              : 
     715              :   !> Set string value by path
     716            1 :   subroutine access_set_string(self, path, val)
     717              :     class(hsd_access_t), intent(inout) :: self
     718              :     character(len=*), intent(in) :: path
     719              :     character(len=*), intent(in) :: val
     720              : 
     721            1 :     integer :: local_stat
     722              : 
     723            1 :     call hsd_set(self%root, path, val, local_stat)
     724            1 :     if (local_stat /= HSD_STAT_OK) &
     725              :         & call self%push_error(path, local_stat, &
     726            0 :         & "Failed to set string value")
     727            0 :   end subroutine access_set_string
     728              : 
     729              :   !> Set integer value by path
     730            1 :   subroutine access_set_integer(self, path, val)
     731              :     class(hsd_access_t), intent(inout) :: self
     732              :     character(len=*), intent(in) :: path
     733              :     integer, intent(in) :: val
     734              : 
     735            1 :     integer :: local_stat
     736              : 
     737            1 :     call hsd_set(self%root, path, val, local_stat)
     738            1 :     if (local_stat /= HSD_STAT_OK) &
     739              :         & call self%push_error(path, local_stat, &
     740            0 :         & "Failed to set integer value")
     741            1 :   end subroutine access_set_integer
     742              : 
     743              :   !> Set double precision real value by path
     744            1 :   subroutine access_set_real_dp(self, path, val)
     745              :     class(hsd_access_t), intent(inout) :: self
     746              :     character(len=*), intent(in) :: path
     747              :     real(dp), intent(in) :: val
     748              : 
     749            1 :     integer :: local_stat
     750              : 
     751            1 :     call hsd_set(self%root, path, val, local_stat)
     752            1 :     if (local_stat /= HSD_STAT_OK) &
     753              :         & call self%push_error(path, local_stat, &
     754            0 :         & "Failed to set real value")
     755            1 :   end subroutine access_set_real_dp
     756              : 
     757              :   !> Set logical value by path
     758            1 :   subroutine access_set_logical(self, path, val)
     759              :     class(hsd_access_t), intent(inout) :: self
     760              :     character(len=*), intent(in) :: path
     761              :     logical, intent(in) :: val
     762              : 
     763            1 :     integer :: local_stat
     764              : 
     765            1 :     call hsd_set(self%root, path, val, local_stat)
     766            1 :     if (local_stat /= HSD_STAT_OK) &
     767              :         & call self%push_error(path, local_stat, &
     768            0 :         & "Failed to set logical value")
     769            1 :   end subroutine access_set_logical
     770              : 
     771              :   !> Set complex double precision value by path
     772            0 :   subroutine access_set_complex_dp(self, path, val)
     773              :     class(hsd_access_t), intent(inout) :: self
     774              :     character(len=*), intent(in) :: path
     775              :     complex(dp), intent(in) :: val
     776              : 
     777            0 :     integer :: local_stat
     778              : 
     779            0 :     call hsd_set(self%root, path, val, local_stat)
     780            0 :     if (local_stat /= HSD_STAT_OK) &
     781              :         & call self%push_error(path, local_stat, &
     782            0 :         & "Failed to set complex value")
     783            1 :   end subroutine access_set_complex_dp
     784              : 
     785              :   ! ===== Array Setters =====
     786              : 
     787              :   !> Set integer array by path
     788            2 :   subroutine access_set_integer_array(self, path, val)
     789              :     class(hsd_access_t), intent(inout) :: self
     790              :     character(len=*), intent(in) :: path
     791              :     integer, intent(in) :: val(:)
     792              : 
     793            1 :     integer :: local_stat
     794              : 
     795            1 :     call hsd_set(self%root, path, val, local_stat)
     796            1 :     if (local_stat /= HSD_STAT_OK) &
     797              :         & call self%push_error(path, local_stat, &
     798            0 :         & "Failed to set integer array")
     799            0 :   end subroutine access_set_integer_array
     800              : 
     801              :   !> Set double precision real array by path
     802            2 :   subroutine access_set_real_dp_array(self, path, val)
     803              :     class(hsd_access_t), intent(inout) :: self
     804              :     character(len=*), intent(in) :: path
     805              :     real(dp), intent(in) :: val(:)
     806              : 
     807            1 :     integer :: local_stat
     808              : 
     809            1 :     call hsd_set(self%root, path, val, local_stat)
     810            1 :     if (local_stat /= HSD_STAT_OK) &
     811              :         & call self%push_error(path, local_stat, &
     812            0 :         & "Failed to set real array")
     813            1 :   end subroutine access_set_real_dp_array
     814              : 
     815              :   !> Set logical array by path
     816            0 :   subroutine access_set_logical_array(self, path, val)
     817              :     class(hsd_access_t), intent(inout) :: self
     818              :     character(len=*), intent(in) :: path
     819              :     logical, intent(in) :: val(:)
     820              : 
     821            0 :     integer :: local_stat
     822              : 
     823            0 :     call hsd_set(self%root, path, val, local_stat)
     824            0 :     if (local_stat /= HSD_STAT_OK) &
     825              :         & call self%push_error(path, local_stat, &
     826            0 :         & "Failed to set logical array")
     827            1 :   end subroutine access_set_logical_array
     828              : 
     829              :   !> Set string array by path
     830            0 :   subroutine access_set_string_array(self, path, val)
     831              :     class(hsd_access_t), intent(inout) :: self
     832              :     character(len=*), intent(in) :: path
     833              :     character(len=*), intent(in) :: val(:)
     834              : 
     835            0 :     integer :: local_stat
     836              : 
     837            0 :     call hsd_set(self%root, path, val, local_stat)
     838            0 :     if (local_stat /= HSD_STAT_OK) &
     839              :         & call self%push_error(path, local_stat, &
     840            0 :         & "Failed to set string array")
     841            0 :   end subroutine access_set_string_array
     842              : 
     843              :   !> Set complex double precision array by path
     844            0 :   subroutine access_set_complex_dp_array(self, path, val)
     845              :     class(hsd_access_t), intent(inout) :: self
     846              :     character(len=*), intent(in) :: path
     847              :     complex(dp), intent(in) :: val(:)
     848              : 
     849            0 :     integer :: local_stat
     850              : 
     851            0 :     call hsd_set(self%root, path, val, local_stat)
     852            0 :     if (local_stat /= HSD_STAT_OK) &
     853              :         & call self%push_error(path, local_stat, &
     854            0 :         & "Failed to set complex array")
     855            0 :   end subroutine access_set_complex_dp_array
     856              : 
     857              :   ! ===== Matrix Setters =====
     858              : 
     859              :   !> Set integer matrix by path
     860            0 :   subroutine access_set_integer_matrix(self, path, val)
     861              :     class(hsd_access_t), intent(inout) :: self
     862              :     character(len=*), intent(in) :: path
     863              :     integer, intent(in) :: val(:,:)
     864              : 
     865            0 :     integer :: local_stat
     866              : 
     867            0 :     call hsd_set(self%root, path, val, local_stat)
     868            0 :     if (local_stat /= HSD_STAT_OK) &
     869              :         & call self%push_error(path, local_stat, &
     870            0 :         & "Failed to set integer matrix")
     871            0 :   end subroutine access_set_integer_matrix
     872              : 
     873              :   !> Set double precision real matrix by path
     874            2 :   subroutine access_set_real_dp_matrix(self, path, val)
     875              :     class(hsd_access_t), intent(inout) :: self
     876              :     character(len=*), intent(in) :: path
     877              :     real(dp), intent(in) :: val(:,:)
     878              : 
     879            1 :     integer :: local_stat
     880              : 
     881            1 :     call hsd_set(self%root, path, val, local_stat)
     882            1 :     if (local_stat /= HSD_STAT_OK) &
     883              :         & call self%push_error(path, local_stat, &
     884            0 :         & "Failed to set real matrix")
     885            0 :   end subroutine access_set_real_dp_matrix
     886              : 
     887              :   !> Set complex double precision matrix by path
     888            0 :   subroutine access_set_complex_dp_matrix(self, path, val)
     889              :     class(hsd_access_t), intent(inout) :: self
     890              :     character(len=*), intent(in) :: path
     891              :     complex(dp), intent(in) :: val(:,:)
     892              : 
     893            0 :     integer :: local_stat
     894              : 
     895            0 :     call hsd_set(self%root, path, val, local_stat)
     896            0 :     if (local_stat /= HSD_STAT_OK) &
     897              :         & call self%push_error(path, local_stat, &
     898            0 :         & "Failed to set complex matrix")
     899            1 :   end subroutine access_set_complex_dp_matrix
     900              : 
     901              :   ! ===== Choice Getter =====
     902              : 
     903              :   !> Get a polymorphic choice (first table child of a block)
     904              :   !>
     905              :   !> Returns the lowercase name of the chosen child table and a pointer to it.
     906              :   !> Pushes an error if the path doesn't exist or contains no table child.
     907            2 :   subroutine access_get_choice(self, path, choice_name, choice_table)
     908              :     class(hsd_access_t), intent(inout) :: self
     909              :     character(len=*), intent(in) :: path
     910              :     character(len=:), allocatable, intent(out) :: choice_name
     911              :     type(hsd_node_t), pointer, intent(out) :: choice_table
     912              : 
     913              :     type(hsd_node_t), pointer :: table_node, child
     914            2 :     integer :: local_stat, ii
     915              : 
     916            2 :     nullify(choice_table)
     917            2 :     choice_name = ""
     918              : 
     919            2 :     call hsd_get_child(self%root, path, table_node, local_stat)
     920            2 :     if (local_stat /= HSD_STAT_OK .or. .not. associated(table_node)) then
     921              :       call self%push_error(path, HSD_STAT_NOT_FOUND, &
     922            1 :           & "Choice block not found")
     923            1 :       return
     924              :     end if
     925              : 
     926            1 :     if (self%mark_processed) table_node%processed = .true.
     927              : 
     928            1 :     if (table_node%node_type /= NODE_TYPE_TABLE) then
     929              :       call self%push_error(path, HSD_STAT_TYPE_ERROR, &
     930            0 :           & "Choice block is not a table")
     931            0 :       return
     932              :     end if
     933              : 
     934            1 :     do ii = 1, table_node%num_children
     935            1 :       call table_node%get_child(ii, child)
     936            1 :       if (.not. associated(child)) cycle
     937            1 :       if (child%node_type == NODE_TYPE_TABLE) then
     938            1 :         choice_table => child
     939            1 :         if (allocated(child%name)) then
     940            1 :           choice_name = to_lower(child%name)
     941              :         end if
     942            1 :         if (self%mark_processed) choice_table%processed = .true.
     943            1 :         return
     944              :       end if
     945              :     end do
     946              : 
     947              :     call self%push_error(path, HSD_STAT_NOT_FOUND, &
     948            0 :         & "No choice found in block")
     949            2 :   end subroutine access_get_choice
     950              : 
     951           80 : end module hsd_access
        

Generated by: LCOV version 2.0-1