| 1 | ! ============================================================================== |
| 2 | ! Main Program: Fortran Shell (Fortsh) |
| 3 | ! ============================================================================== |
| 4 | program fortran_shell |
| 5 | use shell_types |
| 6 | use system_interface |
| 7 | use signal_handler |
| 8 | use parser |
| 9 | use executor |
| 10 | use job_control |
| 11 | use readline |
| 12 | use shell_config |
| 13 | use aliases |
| 14 | use performance |
| 15 | use iso_fortran_env, only: input_unit, output_unit, error_unit |
| 16 | implicit none |
| 17 | |
| 18 | type(shell_state_t) :: shell |
| 19 | type(pipeline_t) :: pipeline |
| 20 | character(len=1024) :: input_line |
| 21 | character(len=:), allocatable :: expanded_line |
| 22 | integer :: iostat, i |
| 23 | |
| 24 | ! Initialize performance monitoring |
| 25 | call init_performance_monitoring() |
| 26 | |
| 27 | ! Initialize shell state |
| 28 | call initialize_shell(shell) |
| 29 | |
| 30 | ! Setup signal handlers if interactive |
| 31 | if (shell%is_interactive) then |
| 32 | call setup_signal_handlers() |
| 33 | |
| 34 | ! Welcome message for interactive mode |
| 35 | write(output_unit, '(a)') 'Welcome to Fortran Shell (fortsh)!' |
| 36 | write(output_unit, '(a)') 'Type "help" for available commands or "exit" to quit.' |
| 37 | write(output_unit, '(a)') '' |
| 38 | |
| 39 | ! Load configuration file |
| 40 | call load_config_file(shell) |
| 41 | end if |
| 42 | |
| 43 | ! Main REPL loop |
| 44 | do while (shell%running) |
| 45 | ! Update job status |
| 46 | if (shell%is_interactive) then |
| 47 | call update_job_status(shell) |
| 48 | call notify_job_status(shell) |
| 49 | end if |
| 50 | |
| 51 | ! Process sourced files |
| 52 | if (shell%should_source) then |
| 53 | call process_source_file(shell) |
| 54 | cycle |
| 55 | end if |
| 56 | |
| 57 | ! Read input with enhanced readline (includes prompt only if interactive) |
| 58 | if (shell%is_interactive) then |
| 59 | call readline_enhanced(trim(shell%username) // '@' // trim(shell%hostname) // ' :: ', input_line, iostat) |
| 60 | else |
| 61 | read(input_unit, '(a)', iostat=iostat) input_line |
| 62 | ! Add to history in non-interactive mode too |
| 63 | if (iostat == 0 .and. len_trim(input_line) > 0) then |
| 64 | call add_to_history(input_line) |
| 65 | end if |
| 66 | end if |
| 67 | |
| 68 | ! Check for EOF (Ctrl-D) |
| 69 | if (iostat /= 0) then |
| 70 | write(output_unit, '(a)') '' |
| 71 | exit |
| 72 | end if |
| 73 | |
| 74 | ! Skip empty lines |
| 75 | if (len_trim(input_line) == 0) cycle |
| 76 | |
| 77 | ! Expand aliases |
| 78 | call expand_alias(shell, trim(input_line), expanded_line) |
| 79 | |
| 80 | ! Parse pipeline |
| 81 | call parse_pipeline(expanded_line, pipeline) |
| 82 | |
| 83 | ! Execute pipeline |
| 84 | if (pipeline%num_commands > 0) then |
| 85 | call execute_pipeline(pipeline, shell, expanded_line) |
| 86 | end if |
| 87 | |
| 88 | ! Clean up pipeline |
| 89 | if (allocated(pipeline%commands)) then |
| 90 | do i = 1, pipeline%num_commands |
| 91 | if (allocated(pipeline%commands(i)%tokens)) deallocate(pipeline%commands(i)%tokens) |
| 92 | if (allocated(pipeline%commands(i)%input_file)) deallocate(pipeline%commands(i)%input_file) |
| 93 | if (allocated(pipeline%commands(i)%output_file)) deallocate(pipeline%commands(i)%output_file) |
| 94 | if (allocated(pipeline%commands(i)%error_file)) deallocate(pipeline%commands(i)%error_file) |
| 95 | if (allocated(pipeline%commands(i)%heredoc_delimiter)) deallocate(pipeline%commands(i)%heredoc_delimiter) |
| 96 | if (allocated(pipeline%commands(i)%heredoc_content)) deallocate(pipeline%commands(i)%heredoc_content) |
| 97 | if (allocated(pipeline%commands(i)%here_string)) deallocate(pipeline%commands(i)%here_string) |
| 98 | end do |
| 99 | |
| 100 | ! Clean up control stack allocatable fields |
| 101 | do i = 1, shell%control_depth |
| 102 | if (allocated(shell%control_stack(i)%for_values)) then |
| 103 | deallocate(shell%control_stack(i)%for_values) |
| 104 | end if |
| 105 | end do |
| 106 | deallocate(pipeline%commands) |
| 107 | end if |
| 108 | end do |
| 109 | |
| 110 | ! Print performance statistics if monitoring was enabled |
| 111 | if (perf_monitoring_enabled) then |
| 112 | call print_performance_stats() |
| 113 | end if |
| 114 | |
| 115 | ! Cleanup performance monitoring |
| 116 | call cleanup_performance_monitoring() |
| 117 | |
| 118 | write(output_unit, '(a)') 'Goodbye!' |
| 119 | |
| 120 | contains |
| 121 | |
| 122 | subroutine process_source_file(shell) |
| 123 | type(shell_state_t), intent(inout) :: shell |
| 124 | character(len=1024) :: input_line |
| 125 | integer :: file_unit, iostat, i |
| 126 | type(pipeline_t) :: pipeline |
| 127 | character(len=:), allocatable :: expanded_line |
| 128 | |
| 129 | ! Reset the source flag first |
| 130 | shell%should_source = .false. |
| 131 | |
| 132 | ! Open file for reading |
| 133 | open(newunit=file_unit, file=trim(shell%source_file), status='old', action='read', iostat=iostat) |
| 134 | if (iostat /= 0) then |
| 135 | write(error_unit, '(a)') 'source: failed to open ' // trim(shell%source_file) |
| 136 | shell%last_exit_status = 1 |
| 137 | return |
| 138 | end if |
| 139 | |
| 140 | ! Execute each line in the file |
| 141 | do |
| 142 | read(file_unit, '(a)', iostat=iostat) input_line |
| 143 | if (iostat /= 0) exit ! End of file or error |
| 144 | |
| 145 | ! Skip empty lines and comments |
| 146 | if (len_trim(input_line) == 0 .or. input_line(1:1) == '#') cycle |
| 147 | |
| 148 | ! Add to history |
| 149 | call add_to_history(input_line) |
| 150 | |
| 151 | ! Expand aliases |
| 152 | call expand_alias(shell, trim(input_line), expanded_line) |
| 153 | |
| 154 | ! Parse and execute pipeline |
| 155 | call parse_pipeline(expanded_line, pipeline) |
| 156 | |
| 157 | if (pipeline%num_commands > 0) then |
| 158 | call execute_pipeline(pipeline, shell, expanded_line) |
| 159 | |
| 160 | ! Clean up pipeline |
| 161 | if (allocated(pipeline%commands)) then |
| 162 | do i = 1, pipeline%num_commands |
| 163 | if (allocated(pipeline%commands(i)%tokens)) deallocate(pipeline%commands(i)%tokens) |
| 164 | if (allocated(pipeline%commands(i)%input_file)) deallocate(pipeline%commands(i)%input_file) |
| 165 | if (allocated(pipeline%commands(i)%output_file)) deallocate(pipeline%commands(i)%output_file) |
| 166 | if (allocated(pipeline%commands(i)%error_file)) deallocate(pipeline%commands(i)%error_file) |
| 167 | if (allocated(pipeline%commands(i)%heredoc_delimiter)) deallocate(pipeline%commands(i)%heredoc_delimiter) |
| 168 | if (allocated(pipeline%commands(i)%heredoc_content)) deallocate(pipeline%commands(i)%heredoc_content) |
| 169 | if (allocated(pipeline%commands(i)%here_string)) deallocate(pipeline%commands(i)%here_string) |
| 170 | end do |
| 171 | deallocate(pipeline%commands) |
| 172 | end if |
| 173 | end if |
| 174 | |
| 175 | ! Stop execution if exit command was encountered |
| 176 | if (.not. shell%running) exit |
| 177 | end do |
| 178 | |
| 179 | close(file_unit) |
| 180 | shell%source_file = '' |
| 181 | end subroutine |
| 182 | |
| 183 | subroutine initialize_shell(shell) |
| 184 | type(shell_state_t), intent(out) :: shell |
| 185 | character(len=:), allocatable :: temp |
| 186 | character(kind=c_char), target :: c_hostname(256) |
| 187 | integer :: ret, i |
| 188 | |
| 189 | ! Get username |
| 190 | temp = get_environment_var('USER') |
| 191 | if (len(temp) > 0) then |
| 192 | shell%username = temp |
| 193 | else |
| 194 | shell%username = 'user' |
| 195 | end if |
| 196 | |
| 197 | ! Get hostname |
| 198 | ret = c_gethostname(c_loc(c_hostname), 256_c_size_t) |
| 199 | if (ret == 0) then |
| 200 | shell%hostname = '' |
| 201 | do i = 1, 256 |
| 202 | if (c_hostname(i) == c_null_char) exit |
| 203 | shell%hostname(i:i) = c_hostname(i) |
| 204 | end do |
| 205 | else |
| 206 | shell%hostname = 'localhost' |
| 207 | end if |
| 208 | |
| 209 | ! Get current directory |
| 210 | shell%cwd = get_current_directory() |
| 211 | |
| 212 | ! Check if shell is interactive |
| 213 | shell%is_interactive = (c_isatty(STDIN_FD) /= 0) |
| 214 | |
| 215 | ! Setup job control if interactive |
| 216 | if (shell%is_interactive) then |
| 217 | shell%shell_pgid = c_getpid() |
| 218 | ret = c_setpgid(shell%shell_pgid, shell%shell_pgid) |
| 219 | shell%shell_terminal = STDIN_FD |
| 220 | ret = c_tcsetpgrp(shell%shell_terminal, shell%shell_pgid) |
| 221 | end if |
| 222 | |
| 223 | ! Initialize other fields |
| 224 | shell%last_exit_status = 0 |
| 225 | shell%last_pid = 0 |
| 226 | shell%running = .true. |
| 227 | shell%num_jobs = 0 |
| 228 | shell%next_job_id = 1 |
| 229 | |
| 230 | ! Initialize jobs array |
| 231 | do i = 1, MAX_JOBS |
| 232 | shell%jobs(i)%job_id = 0 |
| 233 | end do |
| 234 | |
| 235 | ! Check for performance monitoring environment variable |
| 236 | temp = get_environment_var('FORTSH_PERF') |
| 237 | if (len(temp) > 0 .and. trim(temp) == '1') then |
| 238 | call set_performance_monitoring(.true.) |
| 239 | end if |
| 240 | end subroutine |
| 241 | |
| 242 | end program fortran_shell |