program main
use cli, only: parse_arguments
use runner, only: run_fortran_file, is_lazy_fortran_file
use cache, only: clear_cache, get_cache_info
use logger, only: set_verbose_level
! use notebook_types
! use notebook_parser
! use notebook_executor
! use notebook_renderer
use temp_utils, only: create_temp_dir, get_temp_file_path, create_temp_file
use test_cli, only: handle_test_command
implicit none
character(len=256) :: filename, custom_cache_dir, custom_config_dir, notebook_output, custom_flags
logical :: show_help, no_wait, notebook_mode, standardize_only, clear_cache_flag, cache_info_flag
logical :: debug_tokens = .false., debug_ast = .false., debug_semantic = .false.
logical :: debug_standardize = .false., debug_codegen = .false.
logical :: from_tokens, from_ast, from_semantic, show_version
integer :: exit_code, verbose_level, parallel_jobs
! type(notebook_t) :: notebook
! type(execution_result_t) :: results
character(len=256) :: first_arg
! Initialize variables properly
filename = ''
custom_cache_dir = ''
custom_config_dir = ''
notebook_output = ''
custom_flags = ''
! Check for test command first
if (command_argument_count() > 0) then
call get_command_argument(1, first_arg)
if (trim(first_arg) == '--test') then
call handle_test_subcommand()
stop 0
end if
end if
call parse_arguments(filename, show_help, verbose_level, custom_cache_dir, custom_config_dir, &
parallel_jobs, no_wait, notebook_mode, notebook_output, standardize_only, custom_flags, &
clear_cache_flag, cache_info_flag, debug_tokens, debug_ast, debug_semantic, &
debug_standardize, debug_codegen, &
from_tokens, from_ast, from_semantic, show_version)
! Initialize logging based on verbose level
call set_verbose_level(verbose_level)
if (show_help) then
call print_help()
stop 0
end if
if (show_version) then
print '(a)', 'fortrun version 2025.07.git'
stop 0
end if
if (clear_cache_flag) then
call handle_clear_cache(custom_cache_dir, filename, verbose_level)
stop 0
end if
if (cache_info_flag) then
call handle_cache_info(custom_cache_dir)
stop 0
end if
if (standardize_only) then
call handle_standardize_only(filename)
stop 0
end if
! MLIR emission and compilation now handled by fortc package
if (from_tokens .or. from_ast .or. from_semantic) then
print '(a)', 'ERROR: JSON input functionality moved to fortfront package'
print '(a)', 'Use fortfront directly for JSON input processing'
stop 1
end if
! Compilation mode now handled by fortc package
if (notebook_mode) then
print '(a)', 'ERROR: Notebook mode moved to fnb package'
print '(a)', 'Install fnb: https://github.com/lazy-fortran/fnb'
stop 1
else
! Normal execution mode
call run_fortran_file(filename, exit_code, verbose_level, custom_cache_dir, &
custom_config_dir, parallel_jobs, no_wait, custom_flags)
if (exit_code /= 0) then
stop 1
end if
end if
contains
subroutine print_help()
print '(a)', 'Usage: fortrun [options] <file>'
print '(a)', ''
print '(a)', 'Run a Fortran program file with automatic dependency resolution.'
print '(a)', ''
print '(a)', 'Arguments:'
print '(a)', ' <file> Path to Fortran source file'
print '(a)', ' .f90/.F90 = Standard Fortran (no preprocessing)'
print '(a)', ' .lf/.LF = Lowercase Fortran (frontend with type inference)'
print '(a)', ''
print '(a)', 'Options:'
print '(a)', ' -h, --help Show this help message'
print '(a)', ' -v, --verbose 1 Show FPM output'
print '(a)', ' -vv, --verbose 2 Show detailed FPM output'
print '(a)', ' -j, --jobs N (Reserved for future use)'
print '(a)', ' --cache-dir DIR Use custom cache directory'
print '(a)', ' --config-dir DIR Use custom config directory'
print '(a)', ' --flag FLAGS Pass custom flags to FPM compiler'
print '(a)', ' (.f90: user flags only, .lf: opinionated + user flags)'
print '(a)', ' --no-wait Fail immediately if cache is locked'
print '(a)', ' --standardize Output standardized (.lf files to F90) to STDOUT'
print '(a)', ' --emit-f90 Synonym for --standardize'
print '(a)', ' --emit-hlfir Emit HLFIR (High-Level Fortran IR) to STDOUT'
print '(a)', ' --emit-fir Emit FIR (Fortran IR, lowered from HLFIR) to STDOUT'
print '(a)', ' --emit-llvm Emit LLVM IR (lowered from FIR) to STDOUT'
print '(a)', ' --compile Compile to object code via full MLIR pipeline'
print '(a)', ' --enable-ad Enable automatic differentiation with Enzyme'
print '(a)', ' -o, --output FILE Output file for --compile/--emit-* modes'
print '(a)', ''
print '(a)', 'Cache Management:'
print '(a)', ' --clear-cache Clear all cached files'
print '(a)', ' --cache-info Show cache statistics'
print '(a)', ''
print '(a)', 'Notebook Mode:'
print '(a)', ' --notebook Run as notebook with cells and output capture'
print '(a)', ' -o, --output FILE Output markdown file (default: <input>.md)'
print '(a)', ''
print '(a)', 'JSON Pipeline Input:'
print '(a)', ' --from-tokens Read tokens from JSON file (skip lexing)'
print '(a)', ' --from-ast Read AST from JSON file (skip lexing + parsing)'
print '(a)', ' --from-semantic Read semantic AST from JSON file (skip to codegen)'
print '(a)', ''
print '(a)', 'Debug Output (JSON):'
print '(a)', ' --debug-tokens Output tokens as JSON'
print '(a)', ' --debug-ast Output AST as JSON'
print '(a)', ' --debug-semantic Output semantic analysis as JSON'
print '(a)', ' --debug-codegen Output generated code as JSON'
print '(a)', ''
print '(a)', 'Environment:'
print '(a)', ' OMP_NUM_THREADS Number of parallel build threads (FPM uses OpenMP)'
end subroutine print_help
subroutine handle_standardize_only(input_file)
character(len=*), intent(in) :: input_file
character(len=256) :: temp_output, error_msg
character(len=1024) :: line
integer :: unit, ios
logical :: is_lowercase_fortran
! Check if input is a .lf file (lowercase fortran)
is_lowercase_fortran = is_lazy_fortran_file(input_file)
! Create temporary output file
temp_output = create_temp_file('fortran_main_output', '.f90')
! Process based on file type
if (is_lowercase_fortran) then
! Transform using fortfront CLI
block
use system_utils, only: escape_shell_arg
character(len=1024) :: command
integer :: exit_status
command = 'fortfront < "'//trim(escape_shell_arg(input_file))// &
'" > "'//trim(escape_shell_arg(temp_output))//'"'
call execute_command_line(command, exitstat=exit_status, wait=.true.)
if (exit_status /= 0) then
error_msg = 'fortfront transformation failed'
else
error_msg = ''
end if
end block
else
! For standard Fortran files, just copy them as-is
block
use system_utils, only: escape_shell_arg
call execute_command_line('cp "'//trim(escape_shell_arg(input_file))// &
'" "'//trim(escape_shell_arg(temp_output))//'"', exitstat=ios)
end block
if (ios /= 0) then
error_msg = 'Failed to copy file'
else
error_msg = ''
end if
end if
if (len_trim(error_msg) > 0) then
write (*, '(a,a)') 'Error: ', trim(error_msg)
stop 1
end if
! Output the preprocessed content to STDOUT
open (newunit=unit, file=temp_output, status='old', action='read', iostat=ios)
if (ios == 0) then
do
read (unit, '(A)', iostat=ios) line
if (ios /= 0) exit
write (*, '(A)') trim(line)
end do
close (unit)
! Clean up temporary file
open (newunit=unit, file=temp_output, status='old', iostat=ios)
if (ios == 0) then
close (unit, status='delete')
end if
else
write (*, '(a)') 'Error: Failed to read preprocessed output'
stop 1
end if
end subroutine handle_standardize_only
! Compilation functionality moved to fortc package
! MLIR emission functionality moved to fortc package
! JSON input functionality moved to fortfront package
subroutine handle_clear_cache(custom_cache_dir, filename, verbose_level)
character(len=*), intent(in) :: custom_cache_dir
character(len=*), intent(in) :: filename
integer, intent(in) :: verbose_level
logical :: success
integer :: exit_code
if (verbose_level > 0) then
print '(a)', 'Clearing cache...'
end if
call clear_cache(custom_cache_dir, success)
if (success) then
if (verbose_level > 0) then
print '(a)', 'Cache cleared successfully'
end if
! If a filename was provided, run it after clearing cache
if (len_trim(filename) > 0) then
if (verbose_level > 0) then
print '(a)', 'Running file with cleared cache...'
end if
call run_fortran_file(filename, exit_code, verbose_level, custom_cache_dir, &
'', 0, .false., '')
if (exit_code /= 0) then
stop 1
end if
end if
else
print '(a)', 'Error: Failed to clear cache'
stop 1
end if
end subroutine handle_clear_cache
subroutine handle_cache_info(custom_cache_dir)
character(len=*), intent(in) :: custom_cache_dir
character(len=1024) :: info
call get_cache_info(custom_cache_dir, info)
print '(a)', trim(info)
end subroutine handle_cache_info
subroutine handle_test_subcommand()
integer :: nargs, i, test_exit_code
character(len=256), allocatable :: test_args(:)
! Get all arguments except the first one (--test)
nargs = command_argument_count() - 1
if (nargs > 0) then
allocate (test_args(nargs))
do i = 1, nargs
call get_command_argument(i + 1, test_args(i))
end do
call handle_test_command(test_args, test_exit_code)
deallocate (test_args)
else
! No additional arguments, run all tests
allocate (test_args(0))
call handle_test_command(test_args, test_exit_code)
deallocate (test_args)
end if
if (test_exit_code /= 0) then
stop 1
end if
end subroutine handle_test_subcommand
end program main