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

Generated by: LCOV version 2.0-1