subroutine read_mqc_file(filename, config, error)
!! Read and parse a .mqc format input file
character(len=*), intent(in) :: filename
type(mqc_config_t), intent(out) :: config
type(error_t), intent(out) :: error
integer :: unit, io_stat
character(len=MAX_LINE_LEN) :: line
logical :: file_exists
type(error_t) :: parse_error
inquire (file=filename, exist=file_exists)
if (.not. file_exists) then
call error%set(ERROR_IO, "Input file not found: "//trim(filename))
return
end if
open (newunit=unit, file=filename, status='old', action='read', iostat=io_stat)
if (io_stat /= 0) then
call error%set(ERROR_IO, "Error opening input file: "//trim(filename))
return
end if
! Set defaults
config%log_level = "info"
! Read file line by line and dispatch to section parsers
do
read (unit, '(A)', iostat=io_stat) line
if (io_stat /= 0) exit
line = adjustl(line)
if (len_trim(line) == 0) cycle
if (line(1:1) == '#' .or. line(1:1) == '!') cycle
! Check for section start
if (line(1:1) == '%') then
select case (trim(line))
case ('%schema')
call parse_schema_section(unit, config, parse_error)
case ('%model')
call parse_model_section(unit, config, parse_error)
case ('%driver')
call parse_driver_section(unit, config, parse_error)
case ('%structure')
call parse_structure_section(unit, config, parse_error)
case ('%geometry')
call parse_geometry_section(unit, config, parse_error)
case ('%fragments')
call parse_fragments_section(unit, config, parse_error)
case ('%connectivity')
call parse_connectivity_section(unit, config, parse_error)
case ('%scf')
call parse_scf_section(unit, config, parse_error)
case ('%xtb')
call parse_xtb_section(unit, config, parse_error)
case ('%hessian')
call parse_hessian_section(unit, config, parse_error)
case ('%aimd')
call parse_aimd_section(unit, config, parse_error)
case ('%fragmentation')
call parse_fragmentation_section(unit, config, parse_error)
case ('%system')
call parse_system_section(unit, config, parse_error)
case ('%molecules')
call parse_molecules_section(unit, config, parse_error)
case default
! Skip unknown sections
call skip_to_end(unit, parse_error)
end select
if (parse_error%has_error()) then
error = parse_error
call error%add_context("mqc_config_parser:read_mqc_file")
close (unit)
return
end if
end if
end do
close (unit)
! Validate required fields
if (.not. allocated(config%schema_name)) then
call error%set(ERROR_VALIDATION, "Missing required section: %schema")
return
end if
! Validate geometry: required for single-molecule mode, not for multi-molecule mode
if (config%nmol == 0) then
! Single molecule mode: require top-level geometry
if (.not. allocated(config%geometry%coords) .or. config%geometry%natoms == 0) then
call error%set(ERROR_VALIDATION, "Missing required section: %geometry")
return
end if
else
! Multi-molecule mode: each molecule must have geometry (validated during parsing)
! No additional validation needed here
end if
end subroutine read_mqc_file