LCOV - code coverage report
Current view: top level - src/core - hsd_error.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.0 % 100 98
Test Date: 2026-02-04 13:26:36 Functions: 62.5 % 8 5

            Line data    Source code
       1              : !> Error handling for the HSD parser
       2              : !>
       3              : !> This module provides error types and utilities for reporting parsing
       4              : !> errors with file location information.
       5              : module hsd_error
       6              :   use hsd_constants, only: hsd_max_line_length
       7              :   implicit none (type, external)
       8              :   private
       9              : 
      10              :   public :: hsd_error_t, hsd_stat
      11              :   public :: HSD_STAT_OK, HSD_STAT_SYNTAX_ERROR, HSD_STAT_UNCLOSED_TAG
      12              :   public :: HSD_STAT_UNCLOSED_ATTRIB, HSD_STAT_UNCLOSED_QUOTE
      13              :   public :: HSD_STAT_ORPHAN_TEXT, HSD_STAT_INCLUDE_CYCLE, HSD_STAT_INCLUDE_DEPTH
      14              :   public :: HSD_STAT_FILE_NOT_FOUND, HSD_STAT_IO_ERROR, HSD_STAT_TYPE_ERROR
      15              :   public :: HSD_STAT_NOT_FOUND
      16              :   public :: make_error, error_message
      17              :   public :: make_syntax_error, make_type_error
      18              : 
      19              :   !> Status codes
      20              :   integer, parameter :: HSD_STAT_OK = 0
      21              :   integer, parameter :: HSD_STAT_SYNTAX_ERROR = 1
      22              :   integer, parameter :: HSD_STAT_UNCLOSED_TAG = 2
      23              :   integer, parameter :: HSD_STAT_UNCLOSED_ATTRIB = 3
      24              :   integer, parameter :: HSD_STAT_UNCLOSED_QUOTE = 4
      25              :   integer, parameter :: HSD_STAT_ORPHAN_TEXT = 5
      26              :   integer, parameter :: HSD_STAT_INCLUDE_CYCLE = 6
      27              :   integer, parameter :: HSD_STAT_INCLUDE_DEPTH = 7
      28              :   integer, parameter :: HSD_STAT_FILE_NOT_FOUND = 8
      29              :   integer, parameter :: HSD_STAT_IO_ERROR = 9
      30              :   integer, parameter :: HSD_STAT_TYPE_ERROR = 10
      31              :   integer, parameter :: HSD_STAT_NOT_FOUND = 11
      32              : 
      33              :   !> Status type for simpler error handling
      34              :   type :: hsd_stat
      35              :     integer :: code = HSD_STAT_OK
      36              :   end type hsd_stat
      37              : 
      38              :   !> Error type with detailed information
      39              :   type :: hsd_error_t
      40              :     !> Error code
      41              :     integer :: code = HSD_STAT_OK
      42              :     !> Human-readable error message
      43              :     character(len=:), allocatable :: message
      44              :     !> File where error occurred
      45              :     character(len=:), allocatable :: filename
      46              :     !> Line number where error started
      47              :     integer :: line_start = 0
      48              :     !> Line number where error ended
      49              :     integer :: line_end = 0
      50              :     !> Column number where error occurred (optional)
      51              :     integer :: column = 0
      52              :     !> Expected token or value (for context)
      53              :     character(len=:), allocatable :: expected
      54              :     !> Actual token or value that caused error
      55              :     character(len=:), allocatable :: actual
      56              :     !> Hint or suggestion for fixing the error
      57              :     character(len=:), allocatable :: hint
      58              :   contains
      59              :     procedure :: print => error_print
      60              :   end type hsd_error_t
      61              : 
      62              : contains
      63              : 
      64              :   !> Create an error with message and location
      65           75 :   subroutine make_error(error, code, message, filename, line_start, line_end, column, &
      66              :       expected, actual, hint)
      67              :     type(hsd_error_t), allocatable, intent(out) :: error
      68              :     integer, intent(in) :: code
      69              :     character(len=*), intent(in) :: message
      70              :     character(len=*), intent(in), optional :: filename
      71              :     integer, intent(in), optional :: line_start
      72              :     integer, intent(in), optional :: line_end
      73              :     integer, intent(in), optional :: column
      74              :     character(len=*), intent(in), optional :: expected
      75              :     character(len=*), intent(in), optional :: actual
      76              :     character(len=*), intent(in), optional :: hint
      77              : 
      78           75 :     allocate(error)
      79           75 :     error%code = code
      80           75 :     error%message = message
      81              : 
      82           75 :     if (present(filename)) then
      83           20 :       error%filename = filename
      84              :     else
      85           55 :       error%filename = "<unknown>"
      86              :     end if
      87              : 
      88           75 :     if (present(line_start)) then
      89           13 :       error%line_start = line_start
      90              :     end if
      91              : 
      92           75 :     if (present(line_end)) then
      93            2 :       error%line_end = line_end
      94           73 :     else if (present(line_start)) then
      95           11 :       error%line_end = line_start
      96              :     end if
      97              : 
      98           75 :     if (present(column)) then
      99           10 :       error%column = column
     100              :     end if
     101              : 
     102           75 :     if (present(expected)) then
     103            6 :       error%expected = expected
     104              :     end if
     105              : 
     106           75 :     if (present(actual)) then
     107           10 :       error%actual = actual
     108              :     end if
     109              : 
     110           75 :     if (present(hint)) then
     111           10 :       error%hint = hint
     112              :     end if
     113              : 
     114          150 :   end subroutine make_error
     115              : 
     116              :   !> Get a descriptive message for an error code
     117           13 :   pure function error_message(code) result(msg)
     118              :     integer, intent(in) :: code
     119              :     character(len=:), allocatable :: msg
     120              : 
     121           14 :     select case (code)
     122              :     case (HSD_STAT_OK)
     123            1 :       msg = "No error"
     124              :     case (HSD_STAT_SYNTAX_ERROR)
     125            1 :       msg = "Syntax error"
     126              :     case (HSD_STAT_UNCLOSED_TAG)
     127            1 :       msg = "Unclosed tag"
     128              :     case (HSD_STAT_UNCLOSED_ATTRIB)
     129            1 :       msg = "Unclosed attribute"
     130              :     case (HSD_STAT_UNCLOSED_QUOTE)
     131            1 :       msg = "Unclosed quotation"
     132              :     case (HSD_STAT_ORPHAN_TEXT)
     133            1 :       msg = "Orphan text outside of any tag"
     134              :     case (HSD_STAT_INCLUDE_CYCLE)
     135            1 :       msg = "Cyclic include detected"
     136              :     case (HSD_STAT_INCLUDE_DEPTH)
     137            1 :       msg = "Maximum include depth exceeded"
     138              :     case (HSD_STAT_FILE_NOT_FOUND)
     139            1 :       msg = "File not found"
     140              :     case (HSD_STAT_IO_ERROR)
     141            1 :       msg = "I/O error"
     142              :     case (HSD_STAT_TYPE_ERROR)
     143            1 :       msg = "Type conversion error"
     144              :     case (HSD_STAT_NOT_FOUND)
     145            1 :       msg = "Key not found"
     146              :     case default
     147            1 :       msg = "Unknown error"
     148              :     end select
     149              : 
     150           75 :   end function error_message
     151              : 
     152              :   !> Print error to standard output
     153           11 :   subroutine error_print(self, unit)
     154              :     class(hsd_error_t), intent(in) :: self
     155              :     integer, intent(in), optional :: unit
     156              : 
     157           11 :     integer :: out_unit
     158              :     character(len=256) :: line_info, col_info
     159              : 
     160           11 :     if (present(unit)) then
     161            6 :       out_unit = unit
     162              :     else
     163            5 :       out_unit = 6  ! stdout
     164              :     end if
     165              : 
     166              :     ! Format location information
     167           11 :     if (self%line_start > 0) then
     168            8 :       if (self%line_end > self%line_start) then
     169            3 :         write(line_info, '(A,I0,A,I0)') "lines ", self%line_start, "-", self%line_end
     170              :       else
     171            5 :         write(line_info, '(A,I0)') "line ", self%line_start
     172              :       end if
     173              : 
     174            8 :       if (self%column > 0) then
     175            2 :         write(col_info, '(A,I0)') ", column ", self%column
     176              :       else
     177            6 :         col_info = ""
     178              :       end if
     179              : 
     180              :       write(out_unit, '(A,A,A,A,A,A,A)') &
     181            8 :         "Error in '", trim(self%filename), "' at ", trim(line_info), &
     182           16 :         trim(col_info), ": ", self%message
     183              :     else
     184              :       write(out_unit, '(A,A,A,A)') &
     185            3 :         "Error in '", trim(self%filename), "': ", self%message
     186              :     end if
     187              : 
     188              :     ! Print expected vs actual if available
     189           11 :     if (allocated(self%expected) .and. allocated(self%actual)) then
     190            1 :       write(out_unit, '(A,A)') "  Expected: ", self%expected
     191            1 :       write(out_unit, '(A,A)') "  Got:      ", self%actual
     192           10 :     else if (allocated(self%expected)) then
     193            1 :       write(out_unit, '(A,A)') "  Expected: ", self%expected
     194            9 :     else if (allocated(self%actual)) then
     195            2 :       write(out_unit, '(A,A)') "  Got:      ", self%actual
     196              :     end if
     197              : 
     198              :     ! Print hint if available
     199           11 :     if (allocated(self%hint)) then
     200            1 :       write(out_unit, '(A,A)') "  Hint: ", self%hint
     201              :     end if
     202              : 
     203           13 :   end subroutine error_print
     204              : 
     205              :   !> Create a syntax error with expected vs actual context
     206            2 :   subroutine make_syntax_error(error, message, filename, line, column, expected, actual, hint)
     207              :     type(hsd_error_t), allocatable, intent(out) :: error
     208              :     character(len=*), intent(in) :: message
     209              :     character(len=*), intent(in), optional :: filename
     210              :     integer, intent(in), optional :: line
     211              :     integer, intent(in), optional :: column
     212              :     character(len=*), intent(in), optional :: expected
     213              :     character(len=*), intent(in), optional :: actual
     214              :     character(len=*), intent(in), optional :: hint
     215              : 
     216            2 :     allocate(error)
     217            2 :     error%code = HSD_STAT_SYNTAX_ERROR
     218            2 :     error%message = message
     219              : 
     220            2 :     if (present(filename)) then
     221            2 :       error%filename = filename
     222              :     else
     223            0 :       error%filename = "<unknown>"
     224              :     end if
     225              : 
     226            2 :     if (present(line)) then
     227            2 :       error%line_start = line
     228            2 :       error%line_end = line
     229              :     end if
     230              : 
     231            2 :     if (present(column)) then
     232            2 :       error%column = column
     233              :     end if
     234              : 
     235            2 :     if (present(expected)) then
     236            2 :       error%expected = expected
     237              :     end if
     238              : 
     239            2 :     if (present(actual)) then
     240            2 :       error%actual = actual
     241              :     end if
     242              : 
     243            2 :     if (present(hint)) then
     244            2 :       error%hint = hint
     245              :     end if
     246              : 
     247           11 :   end subroutine make_syntax_error
     248              : 
     249              :   !> Create a type error with expected vs actual types
     250            2 :   subroutine make_type_error(error, message, filename, line, expected, actual, hint)
     251              :     type(hsd_error_t), allocatable, intent(out) :: error
     252              :     character(len=*), intent(in) :: message
     253              :     character(len=*), intent(in), optional :: filename
     254              :     integer, intent(in), optional :: line
     255              :     character(len=*), intent(in), optional :: expected
     256              :     character(len=*), intent(in), optional :: actual
     257              :     character(len=*), intent(in), optional :: hint
     258              : 
     259            2 :     allocate(error)
     260            2 :     error%code = HSD_STAT_TYPE_ERROR
     261            2 :     error%message = message
     262              : 
     263            2 :     if (present(filename)) then
     264            2 :       error%filename = filename
     265              :     else
     266            0 :       error%filename = "<unknown>"
     267              :     end if
     268              : 
     269            2 :     if (present(line)) then
     270            2 :       error%line_start = line
     271            2 :       error%line_end = line
     272              :     end if
     273              : 
     274            2 :     if (present(expected)) then
     275            2 :       error%expected = expected
     276              :     end if
     277              : 
     278            2 :     if (present(actual)) then
     279            2 :       error%actual = actual
     280              :     end if
     281              : 
     282            2 :     if (present(hint)) then
     283            2 :       error%hint = hint
     284              :     end if
     285              : 
     286            2 :   end subroutine make_type_error
     287              : 
     288            2 : end module hsd_error
        

Generated by: LCOV version 2.0-1