Fortran · 7818 bytes Raw Blame History
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