LCOV - code coverage report
Current view: top level - src - hsd_value_ops.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.3 % 570 543
Test Date: 2026-03-19 13:06:14 Functions: 100.0 % 31 31

            Line data    Source code
       1              : !> Value operations for HSD types
       2              : !>
       3              : !> This submodule implements all type-bound procedures for hsd_value,
       4              : !> plus private helper routines for parsing arrays, matrices, and
       5              : !> complex numbers. See hsd_types.f90 for type definitions and
       6              : !> interface declarations.
       7              : submodule (hsd_types) hsd_value_ops
       8              :   implicit none (type, external)
       9              : 
      10              : contains
      11              : 
      12              :   ! ===================================================================
      13              :   ! Value setters
      14              :   ! ===================================================================
      15              : 
      16              :   !> Set string value
      17         1000 :   module procedure value_set_string
      18         1000 :     self%value_type = VALUE_TYPE_STRING
      19         1000 :     self%string_value = val
      20         2000 :   end procedure value_set_string
      21              : 
      22              :   !> Set integer value
      23         1346 :   module procedure value_set_integer
      24              :     character(len=30) :: buf
      25         1346 :     write(buf, '(i0)') val
      26         1346 :     self%value_type = VALUE_TYPE_INTEGER
      27         1346 :     self%string_value = trim(buf)
      28         1000 :   end procedure value_set_integer
      29              : 
      30              :   !> Set real value
      31           19 :   module procedure value_set_real
      32              :     character(len=40) :: buf
      33              :     ! ES25.17 preserves full double precision (17 digits)
      34           19 :     write(buf, '(es25.17)') val
      35           19 :     self%value_type = VALUE_TYPE_REAL
      36           19 :     self%string_value = trim(adjustl(buf))
      37         1346 :   end procedure value_set_real
      38              : 
      39              :   !> Set logical value
      40           15 :   module procedure value_set_logical
      41           15 :     self%value_type = VALUE_TYPE_LOGICAL
      42           15 :     if (val) then
      43           13 :       self%string_value = "Yes"
      44              :     else
      45            2 :       self%string_value = "No"
      46              :     end if
      47           19 :   end procedure value_set_logical
      48              : 
      49              :   !> Set complex value
      50            8 :   module procedure value_set_complex
      51              :     character(len=80) :: buf
      52            8 :     write(buf, '("(", es25.17, ",", es25.17, ")")') real(val, dp), aimag(val)
      53            8 :     self%value_type = VALUE_TYPE_COMPLEX
      54            8 :     self%string_value = trim(adjustl(buf))
      55           15 :   end procedure value_set_complex
      56              : 
      57              :   !> Set raw text (for arrays/matrices)
      58              :   !>
      59              :   !> Detects whether the text contains multiple values (space/comma/
      60              :   !> newline separated) and sets VALUE_TYPE_ARRAY accordingly, so that
      61              :   !> hsd_is_array returns the correct result for programmatically set
      62              :   !> arrays.
      63          101 :   module procedure value_set_raw
      64              : 
      65          101 :     integer :: i, token_count
      66          101 :     logical :: in_token
      67              : 
      68              :     ! Quick count of whitespace/comma-separated tokens
      69          101 :     token_count = 0
      70          101 :     in_token = .false.
      71         6690 :     do i = 1, len(text)
      72         6690 :       if (is_separator(text(i:i))) then
      73          856 :         in_token = .false.
      74              :       else
      75         5733 :         if (.not. in_token) then
      76          793 :           token_count = token_count + 1
      77          793 :           in_token = .true.
      78              :         end if
      79              :       end if
      80              :     end do
      81              : 
      82          101 :     if (token_count > 1) then
      83           95 :       self%value_type = VALUE_TYPE_ARRAY
      84              :     else
      85            6 :       self%value_type = VALUE_TYPE_STRING
      86              :     end if
      87          101 :     self%string_value = text
      88              : 
      89            8 :   end procedure value_set_raw
      90              : 
      91              :   ! ===================================================================
      92              :   ! Value getters
      93              :   ! ===================================================================
      94              : 
      95              :   !> Get string value
      96              :   !>
      97              :   !> Returns the string representation of any typed value.
      98              :   !> For natively-typed values (integer, real, logical, complex), the
      99              :   !> value is serialized to a string on the fly.
     100           70 :   module procedure value_get_string
     101              : 
     102           70 :     if (allocated(self%string_value)) then
     103           69 :       val = self%string_value
     104           69 :       if (present(stat)) stat = HSD_STAT_OK
     105              :     else
     106            1 :       val = ""
     107            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     108              :     end if
     109              : 
     110          101 :   end procedure value_get_string
     111              : 
     112              :   !> Get integer value
     113          290 :   module procedure value_get_integer
     114              : 
     115          290 :     integer :: io_stat
     116              : 
     117          290 :     if (allocated(self%string_value)) then
     118          289 :       read(self%string_value, *, iostat=io_stat) val
     119          289 :       if (io_stat /= 0) then
     120            3 :         val = 0
     121            3 :         if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     122              :       else
     123          286 :         if (present(stat)) stat = HSD_STAT_OK
     124              :       end if
     125              :     else
     126            1 :       val = 0
     127            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     128              :     end if
     129              : 
     130           70 :   end procedure value_get_integer
     131              : 
     132              :   !> Get real value
     133           37 :   module procedure value_get_real
     134              : 
     135           37 :     integer :: io_stat
     136              : 
     137           37 :     if (allocated(self%string_value)) then
     138           36 :       read(self%string_value, *, iostat=io_stat) val
     139           36 :       if (io_stat /= 0) then
     140            2 :         val = 0.0_dp
     141            2 :         if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     142              :       else
     143           34 :         if (present(stat)) stat = HSD_STAT_OK
     144              :       end if
     145              :     else
     146            1 :       val = 0.0_dp
     147            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     148              :     end if
     149              : 
     150          290 :   end procedure value_get_real
     151              : 
     152              :   !> Get logical value
     153           35 :   module procedure value_get_logical
     154              : 
     155           35 :     character(len=:), allocatable :: lower_val
     156              : 
     157           35 :     if (allocated(self%string_value)) then
     158           34 :       lower_val = to_lower(trim(self%string_value))
     159           25 :       select case (lower_val)
     160              :       case ("yes", "true", ".true.", "1")
     161           25 :         val = .true.
     162           25 :         if (present(stat)) stat = HSD_STAT_OK
     163              :       case ("no", "false", ".false.", "0")
     164            9 :         val = .false.
     165            9 :         if (present(stat)) stat = HSD_STAT_OK
     166              :       case default
     167            0 :         val = .false.
     168           34 :         if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     169              :       end select
     170              :     else
     171            1 :       val = .false.
     172            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     173              :     end if
     174              : 
     175           72 :   end procedure value_get_logical
     176              : 
     177              :   !> Get complex value
     178              :   !> Parses formats: 4.0+9.0i, 2.0-3.0i, (1.0,2.0), 5.0+2.0j
     179           29 :   module procedure value_get_complex
     180              : 
     181           29 :     if (allocated(self%string_value)) then
     182           28 :       call parse_complex(trim(self%string_value), val, stat)
     183              :     else
     184            1 :       val = (0.0_dp, 0.0_dp)
     185            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     186              :     end if
     187              : 
     188           35 :   end procedure value_get_complex
     189              : 
     190              :   ! ===================================================================
     191              :   ! Array getters
     192              :   ! ===================================================================
     193              : 
     194              :   !> Get integer array from raw text
     195           21 :   module procedure value_get_int_array
     196              : 
     197           21 :     character(len=:), allocatable :: text
     198           21 :     integer :: io_stat
     199              : 
     200              :     ! Get source text
     201           21 :     if (allocated(self%string_value)) then
     202           20 :       text = self%string_value
     203              :     else
     204            1 :       allocate(val(0))
     205            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     206            1 :       return
     207              :     end if
     208              : 
     209              :     ! Count and parse values
     210           20 :     call parse_int_array(text, val, io_stat)
     211           20 :     if (present(stat)) stat = io_stat
     212              : 
     213           50 :   end procedure value_get_int_array
     214              : 
     215              :   !> Get real array from raw text
     216           16 :   module procedure value_get_real_array
     217              : 
     218           16 :     character(len=:), allocatable :: text
     219           16 :     integer :: io_stat
     220              : 
     221              :     ! Get source text
     222           16 :     if (allocated(self%string_value)) then
     223           15 :       text = self%string_value
     224              :     else
     225            1 :       allocate(val(0))
     226            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     227            1 :       return
     228              :     end if
     229              : 
     230              :     ! Count and parse values
     231           15 :     call parse_real_array(text, val, io_stat)
     232           15 :     if (present(stat)) stat = io_stat
     233              : 
     234           37 :   end procedure value_get_real_array
     235              : 
     236              :   !> Get logical array from raw text
     237           14 :   module procedure value_get_logical_array
     238              : 
     239           14 :     character(len=:), allocatable :: text, tokens(:)
     240           14 :     integer :: i, n
     241              : 
     242           14 :     if (allocated(self%string_value)) then
     243           13 :       text = self%string_value
     244              :     else
     245            1 :       allocate(val(0))
     246            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     247            1 :       return
     248              :     end if
     249              : 
     250           13 :     call tokenize_string(text, tokens)
     251           13 :     n = size(tokens)
     252           13 :     allocate(val(n))
     253              : 
     254           49 :     do i = 1, n
     255           87 :       select case (to_lower(trim(tokens(i))))
     256              :       case ("yes")
     257           21 :         val(i) = .true.
     258              :       case ("no")
     259           15 :         val(i) = .false.
     260              :       case default
     261            2 :         val(i) = .false.
     262            2 :         if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     263           78 :         return
     264              :       end select
     265              :     end do
     266              : 
     267           11 :     if (present(stat)) stat = HSD_STAT_OK
     268              : 
     269           43 :   end procedure value_get_logical_array
     270              : 
     271              :   !> Get complex array from raw text
     272           11 :   module procedure value_get_complex_array
     273              : 
     274           11 :     character(len=:), allocatable :: text
     275           11 :     integer :: io_stat
     276              : 
     277              :     ! Get source text
     278           11 :     if (allocated(self%string_value)) then
     279           10 :       text = self%string_value
     280              :     else
     281            1 :       allocate(val(0))
     282            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     283            1 :       return
     284              :     end if
     285              : 
     286              :     ! Count and parse values
     287           10 :     call parse_complex_array(text, val, io_stat)
     288           10 :     if (present(stat)) stat = io_stat
     289              : 
     290           25 :   end procedure value_get_complex_array
     291              : 
     292              :   !> Get string array from raw text (quoted strings preserved)
     293           14 :   module procedure value_get_string_array
     294              : 
     295           14 :     character(len=:), allocatable :: text
     296              : 
     297           14 :     if (allocated(self%string_value)) then
     298           13 :       text = self%string_value
     299              :     else
     300            1 :       allocate(character(len=1) :: val(0))
     301            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     302            1 :       return
     303              :     end if
     304              : 
     305           13 :     call tokenize_quoted_string(text, val)
     306           13 :     if (present(stat)) stat = HSD_STAT_OK
     307              : 
     308           25 :   end procedure value_get_string_array
     309              : 
     310              :   ! ===================================================================
     311              :   ! Matrix getters
     312              :   ! ===================================================================
     313              : 
     314              :   !> Get 2D integer matrix from raw text
     315              :   !> Rows separated by newlines or semicolons.
     316           17 :   module procedure value_get_int_matrix
     317              : 
     318           17 :     character(len=:), allocatable :: text
     319           17 :     integer :: io_stat
     320              : 
     321           17 :     if (allocated(self%string_value)) then
     322           16 :       text = self%string_value
     323              :     else
     324            1 :       allocate(val(0,0))
     325            1 :       nrows = 0
     326            1 :       ncols = 0
     327            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     328            1 :       return
     329              :     end if
     330              : 
     331           16 :     call parse_int_matrix(text, val, nrows, ncols, io_stat)
     332           16 :     if (present(stat)) stat = io_stat
     333              : 
     334           31 :   end procedure value_get_int_matrix
     335              : 
     336              :   !> Get 2D real matrix from raw text
     337           15 :   module procedure value_get_real_matrix
     338              : 
     339           15 :     character(len=:), allocatable :: text
     340           15 :     integer :: io_stat
     341              : 
     342           15 :     if (allocated(self%string_value)) then
     343           14 :       text = self%string_value
     344              :     else
     345            1 :       allocate(val(0,0))
     346            1 :       nrows = 0
     347            1 :       ncols = 0
     348            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     349            1 :       return
     350              :     end if
     351              : 
     352           14 :     call parse_real_matrix(text, val, nrows, ncols, io_stat)
     353           14 :     if (present(stat)) stat = io_stat
     354              : 
     355           32 :   end procedure value_get_real_matrix
     356              : 
     357              :   !> Get 2D complex matrix from raw text
     358              :   !> Rows separated by newlines or semicolons.
     359            4 :   module procedure value_get_complex_matrix
     360              : 
     361            4 :     character(len=:), allocatable :: text
     362            4 :     integer :: io_stat
     363              : 
     364            4 :     if (allocated(self%string_value)) then
     365            4 :       text = self%string_value
     366              :     else
     367            0 :       allocate(val(0,0))
     368            0 :       nrows = 0
     369            0 :       ncols = 0
     370            0 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     371            0 :       return
     372              :     end if
     373              : 
     374            4 :     call parse_complex_matrix(text, val, nrows, ncols, io_stat)
     375            4 :     if (present(stat)) stat = io_stat
     376              : 
     377           19 :   end procedure value_get_complex_matrix
     378              : 
     379              :   ! ===================================================================
     380              :   ! Private helper routines
     381              :   ! ===================================================================
     382              : 
     383              :   !> Check if character is a separator (whitespace, comma, semicolon)
     384        24547 :   pure function is_separator(ch) result(is_sep)
     385              :     character(len=1), intent(in) :: ch
     386              :     logical :: is_sep
     387              :     is_sep = (ch == ' ' .or. ch == char(9) .or. ch == char(10) &
     388        24547 :         & .or. ch == char(13) .or. ch == ',' .or. ch == ';')
     389        24551 :   end function is_separator
     390              : 
     391              :   !> Parse space/comma-separated integers from text
     392           40 :   subroutine parse_int_array(text, arr, stat)
     393              :     character(len=*), intent(in) :: text
     394              :     integer, allocatable, intent(out) :: arr(:)
     395              :     integer, intent(out) :: stat
     396              : 
     397           40 :     character(len=:), allocatable :: tokens(:)
     398           40 :     integer :: i, n, val, io_stat
     399              : 
     400            0 :     call tokenize_string(text, tokens)
     401           40 :     n = size(tokens)
     402              : 
     403           40 :     allocate(arr(n))
     404         1674 :     do i = 1, n
     405         1637 :       read(tokens(i), *, iostat=io_stat) val
     406         1637 :       if (io_stat /= 0) then
     407            3 :         deallocate(arr)
     408            3 :         allocate(arr(0))
     409            3 :         stat = io_stat
     410            3 :         return
     411              :       end if
     412         1671 :       arr(i) = val
     413              :     end do
     414              : 
     415           37 :     stat = 0
     416              : 
     417        24627 :   end subroutine parse_int_array
     418              : 
     419              :   !> Parse space/comma-separated reals from text
     420           36 :   subroutine parse_real_array(text, arr, stat)
     421              :     character(len=*), intent(in) :: text
     422              :     real(dp), allocatable, intent(out) :: arr(:)
     423              :     integer, intent(out) :: stat
     424              : 
     425           36 :     character(len=:), allocatable :: tokens(:)
     426           36 :     integer :: i, n, io_stat
     427           36 :     real(dp) :: val
     428              : 
     429            0 :     call tokenize_string(text, tokens)
     430           36 :     n = size(tokens)
     431              : 
     432           36 :     allocate(arr(n))
     433          131 :     do i = 1, n
     434           98 :       read(tokens(i), *, iostat=io_stat) val
     435           98 :       if (io_stat /= 0) then
     436            3 :         deallocate(arr)
     437            3 :         allocate(arr(0))
     438            3 :         stat = io_stat
     439            3 :         return
     440              :       end if
     441          128 :       arr(i) = val
     442              :     end do
     443              : 
     444           33 :     stat = 0
     445              : 
     446          112 :   end subroutine parse_real_array
     447              : 
     448              :   !> Tokenize string by whitespace and commas.
     449              :   !>
     450              :   !> Uses a two-pass approach (count then fill) to avoid move_alloc on
     451              :   !> deferred-length character arrays, which triggers an Intel ifx bug.
     452          167 :   subroutine tokenize_string(text, tokens)
     453              :     character(len=*), intent(in) :: text
     454              :     character(len=:), allocatable, intent(out) :: tokens(:)
     455              : 
     456          167 :     integer :: i, start, max_len, token_count
     457          167 :     logical :: in_token
     458              : 
     459              :     ! First pass: count tokens and find max length
     460          167 :     token_count = 0
     461          167 :     max_len = 0
     462          167 :     in_token = .false.
     463          167 :     start = 1
     464              : 
     465         8997 :     do i = 1, len(text)
     466         8997 :       if (is_separator(text(i:i))) then
     467         1839 :         if (in_token) then
     468         1814 :           token_count = token_count + 1
     469         1814 :           max_len = max(max_len, i - start)
     470         1814 :           in_token = .false.
     471              :         end if
     472              :       else
     473         6991 :         if (.not. in_token) then
     474         1973 :           start = i
     475         1973 :           in_token = .true.
     476              :         end if
     477              :       end if
     478              :     end do
     479              : 
     480          167 :     if (in_token) then
     481          159 :       token_count = token_count + 1
     482          159 :       max_len = max(max_len, len(text) - start + 1)
     483              :     end if
     484              : 
     485          167 :     if (token_count == 0 .or. max_len == 0) then
     486            0 :       allocate(character(len=1) :: tokens(0))
     487            0 :       return
     488              :     end if
     489              : 
     490              :     ! Allocate result array
     491          167 :     allocate(character(len=max_len) :: tokens(token_count))
     492              : 
     493              :     ! Second pass: extract tokens
     494          167 :     token_count = 0
     495          167 :     in_token = .false.
     496              : 
     497         8997 :     do i = 1, len(text)
     498         8997 :       if (is_separator(text(i:i))) then
     499         1839 :         if (in_token) then
     500         1814 :           token_count = token_count + 1
     501         1814 :           tokens(token_count) = text(start:i-1)
     502         1814 :           in_token = .false.
     503              :         end if
     504              :       else
     505         6991 :         if (.not. in_token) then
     506         1973 :           start = i
     507         1973 :           in_token = .true.
     508              :         end if
     509              :       end if
     510              :     end do
     511              : 
     512          167 :     if (in_token) then
     513          159 :       token_count = token_count + 1
     514          159 :       tokens(token_count) = text(start:len(text))
     515              :     end if
     516              : 
     517          203 :   end subroutine tokenize_string
     518              : 
     519              :   !> Tokenize string preserving quoted sections.
     520              :   !>
     521              :   !> Uses a two-pass approach to avoid move_alloc on deferred-length
     522              :   !> character arrays, which triggers an Intel ifx bug.
     523           13 :   subroutine tokenize_quoted_string(text, tokens)
     524              :     character(len=*), intent(in) :: text
     525              :     character(len=:), allocatable, intent(out) :: tokens(:)
     526              : 
     527           13 :     integer :: i, start, max_len, token_count, tlen
     528              :     character(len=1) :: quote_char
     529           13 :     logical :: in_token, in_quote
     530              : 
     531           13 :     tlen = len_trim(text)
     532              : 
     533              :     ! First pass: count tokens and find max length
     534           13 :     token_count = 0
     535           13 :     max_len = 0
     536           13 :     in_token = .false.
     537           13 :     in_quote = .false.
     538           13 :     quote_char = ' '
     539           13 :     start = 1
     540              : 
     541           13 :     i = 1
     542          191 :     do while (i <= tlen)
     543          178 :       if (in_quote) then
     544           24 :         if (text(i:i) == quote_char) then
     545            5 :           token_count = token_count + 1
     546            5 :           max_len = max(max_len, i - start - 1)
     547            5 :           in_quote = .false.
     548            5 :           in_token = .false.
     549              :         end if
     550          154 :       else if (text(i:i) == '"' .or. text(i:i) == "'") then
     551            5 :         quote_char = text(i:i)
     552            5 :         in_quote = .true.
     553            5 :         start = i
     554            5 :         in_token = .true.
     555          149 :       else if (is_separator(text(i:i))) then
     556           23 :         if (in_token) then
     557           19 :           token_count = token_count + 1
     558           19 :           max_len = max(max_len, i - start)
     559           19 :           in_token = .false.
     560              :         end if
     561              :       else
     562          126 :         if (.not. in_token) then
     563           29 :           start = i
     564           29 :           in_token = .true.
     565              :         end if
     566              :       end if
     567          178 :       i = i + 1
     568              :     end do
     569              : 
     570           13 :     if (in_token .and. .not. in_quote) then
     571           10 :       token_count = token_count + 1
     572           10 :       max_len = max(max_len, tlen - start + 1)
     573              :     end if
     574              : 
     575           13 :     if (token_count == 0 .or. max_len == 0) then
     576            1 :       allocate(character(len=1) :: tokens(0))
     577            1 :       return
     578              :     end if
     579              : 
     580              :     ! Allocate result array
     581           12 :     allocate(character(len=max_len) :: tokens(token_count))
     582              : 
     583              :     ! Second pass: extract tokens
     584           12 :     token_count = 0
     585           12 :     in_token = .false.
     586           12 :     in_quote = .false.
     587           12 :     quote_char = ' '
     588              : 
     589           12 :     i = 1
     590          190 :     do while (i <= tlen)
     591          178 :       if (in_quote) then
     592           24 :         if (text(i:i) == quote_char) then
     593            5 :           token_count = token_count + 1
     594            5 :           if (i > start + 1) then
     595            4 :             tokens(token_count) = text(start+1:i-1)
     596              :           else
     597            1 :             tokens(token_count) = ""
     598              :           end if
     599            5 :           in_quote = .false.
     600            5 :           in_token = .false.
     601              :         end if
     602          154 :       else if (text(i:i) == '"' .or. text(i:i) == "'") then
     603            5 :         quote_char = text(i:i)
     604            5 :         in_quote = .true.
     605            5 :         start = i
     606            5 :         in_token = .true.
     607          149 :       else if (is_separator(text(i:i))) then
     608           23 :         if (in_token) then
     609           19 :           token_count = token_count + 1
     610           19 :           tokens(token_count) = text(start:i-1)
     611           19 :           in_token = .false.
     612              :         end if
     613              :       else
     614          126 :         if (.not. in_token) then
     615           29 :           start = i
     616           29 :           in_token = .true.
     617              :         end if
     618              :       end if
     619          178 :       i = i + 1
     620              :     end do
     621              : 
     622           12 :     if (in_token .and. .not. in_quote) then
     623           10 :       token_count = token_count + 1
     624           10 :       tokens(token_count) = text(start:tlen)
     625              :     end if
     626              : 
     627          180 :   end subroutine tokenize_quoted_string
     628              : 
     629              :   !> Parse 2D integer matrix (rows by newlines or semicolons)
     630           16 :   subroutine parse_int_matrix(text, mat, nrows, ncols, stat)
     631              :     character(len=*), intent(in) :: text
     632              :     integer, allocatable, intent(out) :: mat(:,:)
     633              :     integer, intent(out) :: nrows, ncols, stat
     634              : 
     635           16 :     character(len=:), allocatable :: rows(:)
     636           16 :     integer, allocatable :: row_vals(:)
     637           16 :     integer :: i, j
     638              : 
     639            0 :     call count_matrix_dims(text, rows, nrows, ncols, stat)
     640              : 
     641           16 :     if (nrows == 0 .or. ncols == 0) then
     642            2 :       allocate(mat(0,0))
     643            2 :       nrows = 0
     644            2 :       ncols = 0
     645            2 :       stat = 0
     646            2 :       return
     647              :     end if
     648              : 
     649           14 :     if (stat /= 0) then
     650              :       ! Ragged matrix: return error but preserve dimension info
     651            3 :       allocate(mat(0,0))
     652            3 :       return
     653              :     end if
     654              : 
     655           11 :     allocate(mat(nrows, ncols))
     656          105 :     mat = 0
     657              : 
     658           11 :     j = 0
     659           32 :     do i = 1, size(rows)
     660           32 :       if (len_trim(rows(i)) > 0) then
     661           20 :         call parse_int_array(rows(i), row_vals, stat)
     662           20 :         if (stat /= 0) then
     663            1 :           deallocate(mat)
     664            1 :           allocate(mat(0,0))
     665            1 :           nrows = 0
     666            1 :           ncols = 0
     667            1 :           return
     668              :         end if
     669           19 :         if (size(row_vals) > 0) then
     670           19 :           j = j + 1
     671           77 :           mat(j, 1:size(row_vals)) = row_vals
     672              :         end if
     673              :       end if
     674              :     end do
     675              : 
     676           10 :     stat = 0
     677              : 
     678           45 :   end subroutine parse_int_matrix
     679              : 
     680              :   !> Parse 2D real matrix
     681           14 :   subroutine parse_real_matrix(text, mat, nrows, ncols, stat)
     682              :     character(len=*), intent(in) :: text
     683              :     real(dp), allocatable, intent(out) :: mat(:,:)
     684              :     integer, intent(out) :: nrows, ncols, stat
     685              : 
     686           14 :     character(len=:), allocatable :: rows(:)
     687           14 :     real(dp), allocatable :: row_vals(:)
     688           14 :     integer :: i, j
     689              : 
     690            0 :     call count_matrix_dims(text, rows, nrows, ncols, stat)
     691              : 
     692           14 :     if (nrows == 0 .or. ncols == 0) then
     693            1 :       allocate(mat(0,0))
     694            1 :       nrows = 0
     695            1 :       ncols = 0
     696            1 :       stat = 0
     697            1 :       return
     698              :     end if
     699              : 
     700           13 :     if (stat /= 0) then
     701              :       ! Ragged matrix: return error but preserve dimension info
     702            2 :       allocate(mat(0,0))
     703            2 :       return
     704              :     end if
     705              : 
     706           11 :     allocate(mat(nrows, ncols))
     707           88 :     mat = 0.0_dp
     708              : 
     709           11 :     j = 0
     710           31 :     do i = 1, size(rows)
     711           31 :       if (len_trim(rows(i)) > 0) then
     712           21 :         call parse_real_array(rows(i), row_vals, stat)
     713           21 :         if (stat /= 0) then
     714            1 :           deallocate(mat)
     715            1 :           allocate(mat(0,0))
     716            1 :           nrows = 0
     717            1 :           ncols = 0
     718            1 :           return
     719              :         end if
     720           20 :         if (size(row_vals) > 0) then
     721           20 :           j = j + 1
     722           68 :           mat(j, 1:size(row_vals)) = row_vals
     723              :         end if
     724              :       end if
     725              :     end do
     726              : 
     727           10 :     stat = 0
     728              : 
     729           44 :   end subroutine parse_real_matrix
     730              : 
     731              :   !> Count matrix dimensions from text (rows by newlines/semicolons)
     732              :   !>
     733              :   !> Shared helper for parse_int_matrix and parse_real_matrix.
     734              :   !> Splits text into rows, counts non-empty rows, and validates
     735              :   !> that all rows have the same number of columns.
     736              :   !>
     737              :   !> @param[in]  text   Raw text to parse
     738              :   !> @param[out] rows   Rows split by newlines (for caller to parse)
     739              :   !> @param[out] nrows  Number of non-empty rows
     740              :   !> @param[out] ncols  Number of columns (from first row)
     741              :   !> @param[out] stat   0 on success, HSD_STAT_TYPE_ERROR on ragged
     742           34 :   subroutine count_matrix_dims(text, rows, nrows, ncols, stat)
     743              :     character(len=*), intent(in) :: text
     744              :     character(len=:), allocatable, intent(out) :: rows(:)
     745              :     integer, intent(out) :: nrows, ncols, stat
     746              : 
     747           34 :     character(len=:), allocatable :: tokens(:)
     748           34 :     integer :: i, row_count, col_count, first_cols
     749              : 
     750            0 :     call split_by_newlines(text, rows)
     751           34 :     row_count = size(rows)
     752              : 
     753           34 :     nrows = 0
     754           34 :     ncols = 0
     755           34 :     first_cols = -1
     756           34 :     stat = 0
     757              : 
     758           99 :     do i = 1, row_count
     759           99 :       if (len_trim(rows(i)) > 0) then
     760           60 :         call tokenize_string(rows(i), tokens)
     761           60 :         col_count = size(tokens)
     762           60 :         if (col_count > 0) then
     763           60 :           nrows = nrows + 1
     764           60 :           if (first_cols < 0) then
     765           31 :             first_cols = col_count
     766           31 :             ncols = col_count
     767           29 :           else if (col_count /= first_cols) then
     768              :             ! Ragged matrix: flag error but preserve dimension info
     769            6 :             ncols = max(ncols, col_count)
     770            6 :             stat = HSD_STAT_TYPE_ERROR
     771              :           end if
     772              :         end if
     773              :       end if
     774              :     end do
     775              : 
     776           48 :   end subroutine count_matrix_dims
     777              : 
     778              :   !> Split text by newlines or semicolons.
     779              :   !>
     780              :   !> Uses a two-pass approach to avoid move_alloc on deferred-length
     781              :   !> character arrays, which triggers an Intel ifx bug.
     782           34 :   subroutine split_by_newlines(text, lines)
     783              :     character(len=*), intent(in) :: text
     784              :     character(len=:), allocatable, intent(out) :: lines(:)
     785              : 
     786           34 :     integer :: i, start, line_count, max_len, tlen
     787              : 
     788           34 :     tlen = len(text)
     789              : 
     790              :     ! First pass: count lines and find max length
     791           34 :     line_count = 0
     792           34 :     max_len = 0
     793           34 :     start = 1
     794              : 
     795          898 :     do i = 1, tlen
     796          898 :       if (text(i:i) == char(10) .or. text(i:i) == ';') then
     797           31 :         line_count = line_count + 1
     798           31 :         max_len = max(max_len, i - start)
     799           31 :         start = i + 1
     800              :       end if
     801              :     end do
     802              : 
     803           34 :     if (start <= tlen) then
     804           33 :       line_count = line_count + 1
     805           33 :       max_len = max(max_len, tlen - start + 1)
     806              :     end if
     807              : 
     808           34 :     if (line_count == 0 .or. max_len == 0) then
     809            1 :       allocate(character(len=max(1, len(text))) :: lines(1))
     810            1 :       lines(1) = text
     811            1 :       return
     812              :     end if
     813              : 
     814              :     ! Allocate result array
     815           33 :     allocate(character(len=max_len) :: lines(line_count))
     816              : 
     817              :     ! Second pass: extract lines
     818           33 :     line_count = 0
     819           33 :     start = 1
     820              : 
     821          897 :     do i = 1, tlen
     822          897 :       if (text(i:i) == char(10) .or. text(i:i) == ';') then
     823           31 :         line_count = line_count + 1
     824           31 :         if (i > start) then
     825           29 :           lines(line_count) = text(start:i-1)
     826              :         else
     827            2 :           lines(line_count) = ""
     828              :         end if
     829           31 :         start = i + 1
     830              :       end if
     831              :     end do
     832              : 
     833           33 :     if (start <= tlen) then
     834           33 :       line_count = line_count + 1
     835           33 :       lines(line_count) = text(start:tlen)
     836              :     end if
     837              : 
     838           68 :   end subroutine split_by_newlines
     839              : 
     840              :   !> Parse 2D complex matrix
     841            4 :   subroutine parse_complex_matrix(text, mat, nrows, ncols, stat)
     842              :     character(len=*), intent(in) :: text
     843              :     complex(dp), allocatable, intent(out) :: mat(:,:)
     844              :     integer, intent(out) :: nrows, ncols, stat
     845              : 
     846            4 :     character(len=:), allocatable :: rows(:)
     847            4 :     complex(dp), allocatable :: row_vals(:)
     848            4 :     integer :: i, j
     849              : 
     850            0 :     call count_matrix_dims(text, rows, nrows, ncols, stat)
     851              : 
     852            4 :     if (nrows == 0 .or. ncols == 0) then
     853            0 :       allocate(mat(0,0))
     854            0 :       nrows = 0
     855            0 :       ncols = 0
     856            0 :       stat = 0
     857            0 :       return
     858              :     end if
     859              : 
     860            4 :     if (stat /= 0) then
     861              :       ! Ragged matrix: return error but preserve dimension info
     862            0 :       allocate(mat(0,0))
     863            0 :       return
     864              :     end if
     865              : 
     866            4 :     allocate(mat(nrows, ncols))
     867           34 :     mat = (0.0_dp, 0.0_dp)
     868              : 
     869            4 :     j = 0
     870           12 :     do i = 1, size(rows)
     871           12 :       if (len_trim(rows(i)) > 0) then
     872            8 :         call parse_complex_array(rows(i), row_vals, stat)
     873            8 :         if (stat /= 0) then
     874            0 :           deallocate(mat)
     875            0 :           allocate(mat(0,0))
     876            0 :           nrows = 0
     877            0 :           ncols = 0
     878            0 :           return
     879              :         end if
     880            8 :         if (size(row_vals) > 0) then
     881            8 :           j = j + 1
     882           28 :           mat(j, 1:size(row_vals)) = row_vals
     883              :         end if
     884              :       end if
     885              :     end do
     886              : 
     887            4 :     stat = 0
     888              : 
     889           42 :   end subroutine parse_complex_matrix
     890              : 
     891              :   !> Parse a single complex number from string
     892              :   !> Supports: 4.0+9.0i, 2.0-3.0i, (1.0,2.0), 5.0+2.0j, 3.5,
     893              :   !> pure imaginary 2.0i
     894           69 :   subroutine parse_complex(str, val, stat)
     895              :     character(len=*), intent(in) :: str
     896              :     complex(dp), intent(out) :: val
     897              :     integer, intent(out), optional :: stat
     898              : 
     899           69 :     character(len=:), allocatable :: work
     900           69 :     integer :: i, sign_pos, io_stat
     901           69 :     real(dp) :: re, im
     902              :     character(len=1) :: ch
     903              : 
     904           69 :     work = adjustl(trim(str))
     905              : 
     906              :     ! Handle empty string
     907           69 :     if (len_trim(work) == 0) then
     908            1 :       val = (0.0_dp, 0.0_dp)
     909            1 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     910            1 :       return
     911              :     end if
     912              : 
     913              :     ! Handle Fortran-style (re,im) format
     914           68 :     if (work(1:1) == '(') then
     915            8 :       i = index(work, ')')
     916            8 :       if (i > 2) then
     917            8 :         work = work(2:i-1)
     918            8 :         i = index(work, ',')
     919            8 :         if (i > 0) then
     920            8 :           read(work(1:i-1), *, iostat=io_stat) re
     921            8 :           if (io_stat /= 0) then
     922            1 :             val = (0.0_dp, 0.0_dp)
     923            1 :             if (present(stat)) stat = io_stat
     924            1 :             return
     925              :           end if
     926            7 :           read(work(i+1:), *, iostat=io_stat) im
     927            7 :           if (io_stat /= 0) then
     928            2 :             val = (0.0_dp, 0.0_dp)
     929            2 :             if (present(stat)) stat = io_stat
     930            2 :             return
     931              :           end if
     932            5 :           val = cmplx(re, im, dp)
     933            5 :           if (present(stat)) stat = HSD_STAT_OK
     934            5 :           return
     935              :         end if
     936              :       end if
     937              :     end if
     938              : 
     939              :     ! Handle a+bi or a-bi format (also handles j instead of i)
     940              :     ! Find the + or - that separates real and imaginary parts
     941              :     ! (must skip the first char and any exponent signs)
     942           60 :     sign_pos = 0
     943          670 :     do i = 2, len_trim(work)
     944          610 :       ch = work(i:i)
     945          670 :       if ((ch == '+' .or. ch == '-')) then
     946              :         ! Make sure this isn't part of an exponent
     947           51 :         if (i > 1) then
     948              :           if (work(i-1:i-1) /= 'e' &
     949              :               & .and. work(i-1:i-1) /= 'E' &
     950              :               & .and. work(i-1:i-1) /= 'd' &
     951           51 :               & .and. work(i-1:i-1) /= 'D') then
     952           51 :             sign_pos = i
     953              :           end if
     954              :         end if
     955              :       end if
     956              :     end do
     957              : 
     958              :     ! Check if last character is 'i' or 'j' (imaginary marker)
     959           60 :     ch = work(len_trim(work):len_trim(work))
     960              :     if (ch == 'i' .or. ch == 'I' &
     961           60 :         & .or. ch == 'j' .or. ch == 'J') then
     962           55 :       if (sign_pos > 0) then
     963              :         ! Format: a+bi or a-bi
     964           51 :         read(work(1:sign_pos-1), *, iostat=io_stat) re
     965           51 :         if (io_stat /= 0) then
     966            1 :           val = (0.0_dp, 0.0_dp)
     967            1 :           if (present(stat)) stat = io_stat
     968            1 :           return
     969              :         end if
     970           50 :         read(work(sign_pos:len_trim(work)-1), *, &
     971          100 :             & iostat=io_stat) im
     972           50 :         if (io_stat /= 0) then
     973            2 :           val = (0.0_dp, 0.0_dp)
     974            2 :           if (present(stat)) stat = io_stat
     975            2 :           return
     976              :         end if
     977           48 :         val = cmplx(re, im, dp)
     978           48 :         if (present(stat)) stat = HSD_STAT_OK
     979           48 :         return
     980              :       else
     981              :         ! Pure imaginary: bi
     982            4 :         read(work(1:len_trim(work)-1), *, iostat=io_stat) im
     983            4 :         if (io_stat /= 0) then
     984            1 :           val = (0.0_dp, 0.0_dp)
     985            1 :           if (present(stat)) stat = io_stat
     986            1 :           return
     987              :         end if
     988            3 :         val = cmplx(0.0_dp, im, dp)
     989            3 :         if (present(stat)) stat = HSD_STAT_OK
     990            3 :         return
     991              :       end if
     992              :     else
     993              :       ! Pure real number
     994            5 :       read(work, *, iostat=io_stat) re
     995            5 :       if (io_stat /= 0) then
     996            3 :         val = (0.0_dp, 0.0_dp)
     997            3 :         if (present(stat)) stat = io_stat
     998            3 :         return
     999              :       end if
    1000            2 :       val = cmplx(re, 0.0_dp, dp)
    1001            2 :       if (present(stat)) stat = HSD_STAT_OK
    1002              :     end if
    1003              : 
    1004           73 :   end subroutine parse_complex
    1005              : 
    1006              :   !> Parse an array of complex numbers from text
    1007           18 :   subroutine parse_complex_array(text, arr, stat)
    1008              :     character(len=*), intent(in) :: text
    1009              :     complex(dp), allocatable, intent(out) :: arr(:)
    1010              :     integer, intent(out) :: stat
    1011              : 
    1012           18 :     character(len=:), allocatable :: tokens(:)
    1013           18 :     integer :: i, n, io_stat
    1014           18 :     complex(dp) :: val
    1015              : 
    1016            0 :     call tokenize_string(text, tokens)
    1017           18 :     n = size(tokens)
    1018              : 
    1019           18 :     allocate(arr(n))
    1020           57 :     do i = 1, n
    1021           41 :       call parse_complex(tokens(i), val, io_stat)
    1022           41 :       if (io_stat /= 0) then
    1023            2 :         deallocate(arr)
    1024            2 :         allocate(arr(0))
    1025            2 :         stat = io_stat
    1026            2 :         return
    1027              :       end if
    1028           96 :       arr(i) = val
    1029              :     end do
    1030              : 
    1031           16 :     stat = 0
    1032              : 
    1033          105 :   end subroutine parse_complex_array
    1034              : 
    1035            8 : end submodule hsd_value_ops
        

Generated by: LCOV version 2.0-1