Fortran · 28038 bytes Raw Blame History
1 ! ==============================================================================
2 ! Module: builtins (Extended with job control)
3 ! ==============================================================================
4 module builtins
5 use shell_types
6 use system_interface
7 use job_control
8 use test_builtin
9 use readline
10 use shell_config
11 use aliases
12 use performance
13 use parser
14 use iso_fortran_env, only: output_unit, error_unit
15 implicit none
16
17 contains
18
19 function is_builtin(cmd_name) result(is_built)
20 character(len=*), intent(in) :: cmd_name
21 logical :: is_built
22
23 is_built = (trim(cmd_name) == 'exit' .or. &
24 trim(cmd_name) == 'cd' .or. &
25 trim(cmd_name) == 'pwd' .or. &
26 trim(cmd_name) == 'export' .or. &
27 trim(cmd_name) == 'echo' .or. &
28 trim(cmd_name) == 'jobs' .or. &
29 trim(cmd_name) == 'fg' .or. &
30 trim(cmd_name) == 'bg' .or. &
31 trim(cmd_name) == 'source' .or. &
32 trim(cmd_name) == '.' .or. &
33 trim(cmd_name) == 'history' .or. &
34 trim(cmd_name) == 'kill' .or. &
35 trim(cmd_name) == 'wait' .or. &
36 trim(cmd_name) == 'trap' .or. &
37 trim(cmd_name) == 'config' .or. &
38 trim(cmd_name) == 'alias' .or. &
39 trim(cmd_name) == 'unalias' .or. &
40 trim(cmd_name) == 'help' .or. &
41 trim(cmd_name) == 'perf' .or. &
42 trim(cmd_name) == 'memory' .or. &
43 trim(cmd_name) == 'rawtest' .or. &
44 is_test_command(cmd_name))
45 end function
46
47 subroutine execute_builtin(cmd, shell)
48 type(command_t), intent(in) :: cmd
49 type(shell_state_t), intent(inout) :: shell
50
51 select case(trim(cmd%tokens(1)))
52 case('exit')
53 call builtin_exit(cmd, shell)
54 case('cd')
55 call builtin_cd(cmd, shell)
56 case('pwd')
57 call builtin_pwd(cmd, shell)
58 case('export')
59 call builtin_export(cmd, shell)
60 case('echo')
61 call builtin_echo(cmd, shell)
62 case('jobs')
63 call builtin_jobs(cmd, shell)
64 case('fg')
65 call builtin_fg(cmd, shell)
66 case('bg')
67 call builtin_bg(cmd, shell)
68 case('source', '.')
69 call builtin_source(cmd, shell)
70 case('history')
71 call builtin_history(cmd, shell)
72 case('kill')
73 call builtin_kill(cmd, shell)
74 case('wait')
75 call builtin_wait(cmd, shell)
76 case('trap')
77 call builtin_trap(cmd, shell)
78 case('config')
79 call builtin_config(cmd, shell)
80 case('alias')
81 call builtin_alias(cmd, shell)
82 case('unalias')
83 call builtin_unalias(cmd, shell)
84 case('help')
85 call builtin_help(cmd, shell)
86 case('perf')
87 call builtin_perf(cmd, shell)
88 case('memory')
89 call builtin_memory(cmd, shell)
90 case('rawtest')
91 call builtin_rawtest(cmd, shell)
92 case('test', '[', '[[')
93 call execute_test_command(cmd, shell)
94 case default
95 ! Should not reach here if is_builtin works correctly
96 shell%last_exit_status = 1
97 end select
98 end subroutine
99
100 subroutine builtin_exit(cmd, shell)
101 type(command_t), intent(in) :: cmd
102 type(shell_state_t), intent(inout) :: shell
103
104 shell%running = .false.
105 if (cmd%num_tokens > 1) then
106 read(cmd%tokens(2), *, iostat=shell%last_exit_status) shell%last_exit_status
107 end if
108 end subroutine
109
110 subroutine builtin_cd(cmd, shell)
111 type(command_t), intent(in) :: cmd
112 type(shell_state_t), intent(inout) :: shell
113 character(len=:), allocatable :: target_dir
114
115 if (cmd%num_tokens == 1) then
116 target_dir = get_environment_var('HOME')
117 else
118 target_dir = trim(cmd%tokens(2))
119 end if
120
121 if (change_directory(target_dir)) then
122 shell%cwd = get_current_directory()
123 shell%last_exit_status = 0
124 else
125 write(error_unit, '(a)') 'cd: cannot access ' // trim(target_dir) // &
126 ': No such file or directory. Use "pwd" to see current location.'
127 shell%last_exit_status = 1
128 end if
129 end subroutine
130
131 subroutine builtin_pwd(cmd, shell)
132 type(command_t), intent(in) :: cmd
133 type(shell_state_t), intent(inout) :: shell
134
135 write(output_unit, '(a)') trim(shell%cwd)
136 shell%last_exit_status = 0
137 end subroutine
138
139 subroutine builtin_export(cmd, shell)
140 type(command_t), intent(in) :: cmd
141 type(shell_state_t), intent(inout) :: shell
142 integer :: eq_pos
143 character(len=MAX_TOKEN_LEN) :: var_name, var_value
144
145 if (cmd%num_tokens < 2) then
146 write(error_unit, '(a)') 'export: usage: export VAR=value'
147 shell%last_exit_status = 1
148 return
149 end if
150
151 eq_pos = index(cmd%tokens(2), '=')
152 if (eq_pos > 0) then
153 var_name = cmd%tokens(2)(:eq_pos-1)
154 var_value = cmd%tokens(2)(eq_pos+1:)
155
156 if (set_environment_var(trim(var_name), trim(var_value))) then
157 shell%last_exit_status = 0
158 else
159 write(error_unit, '(a)') 'export: failed to set variable'
160 shell%last_exit_status = 1
161 end if
162 else
163 write(error_unit, '(a)') 'export: usage: export VAR=value'
164 shell%last_exit_status = 1
165 end if
166 end subroutine
167
168 subroutine builtin_echo(cmd, shell)
169 type(command_t), intent(in) :: cmd
170 type(shell_state_t), intent(inout) :: shell
171 integer :: i
172 logical :: first
173
174 ! Simple echo implementation
175 if (.not. allocated(cmd%tokens) .or. cmd%num_tokens < 1) then
176 write(*,'(a)') ''
177 shell%last_exit_status = 0
178 return
179 end if
180
181 first = .true.
182 do i = 2, cmd%num_tokens
183 if (.not. first) write(*,'(a)',advance='no') ' '
184 write(*,'(a)',advance='no') trim(cmd%tokens(i))
185 first = .false.
186 end do
187 write(*,'(a)') ''
188
189 shell%last_exit_status = 0
190 end subroutine
191
192 subroutine builtin_jobs(cmd, shell)
193 type(command_t), intent(in) :: cmd
194 type(shell_state_t), intent(inout) :: shell
195 logical :: show_pids
196
197 ! Check for -p flag to show PIDs
198 show_pids = .false.
199 if (cmd%num_tokens > 1 .and. trim(cmd%tokens(2)) == '-p') then
200 show_pids = .true.
201 end if
202
203 call list_jobs(shell, show_pids)
204 shell%last_exit_status = 0
205 end subroutine
206
207 subroutine builtin_fg(cmd, shell)
208 type(command_t), intent(in) :: cmd
209 type(shell_state_t), intent(inout) :: shell
210 integer :: job_id, iostat, i
211
212 if (cmd%num_tokens < 2) then
213 ! Bring most recent stopped job to foreground
214 job_id = 0
215 do i = MAX_JOBS, 1, -1
216 if (shell%jobs(i)%job_id > 0 .and. shell%jobs(i)%state == JOB_STOPPED) then
217 job_id = shell%jobs(i)%job_id
218 exit
219 end if
220 end do
221
222 if (job_id == 0) then
223 write(error_unit, '(a)') 'fg: no stopped job'
224 shell%last_exit_status = 1
225 return
226 end if
227 else
228 ! Parse job number (handle %n syntax)
229 if (cmd%tokens(2)(1:1) == '%') then
230 read(cmd%tokens(2)(2:), *, iostat=iostat) job_id
231 else
232 read(cmd%tokens(2), *, iostat=iostat) job_id
233 end if
234
235 if (iostat /= 0) then
236 write(error_unit, '(a)') 'fg: invalid job id'
237 shell%last_exit_status = 1
238 return
239 end if
240 end if
241
242 call resume_job_fg(shell, job_id)
243 end subroutine
244
245 subroutine builtin_bg(cmd, shell)
246 type(command_t), intent(in) :: cmd
247 type(shell_state_t), intent(inout) :: shell
248 integer :: job_id, iostat, i
249
250 if (cmd%num_tokens < 2) then
251 ! Continue most recent stopped job in background
252 job_id = 0
253 do i = MAX_JOBS, 1, -1
254 if (shell%jobs(i)%job_id > 0 .and. &
255 shell%jobs(i)%state == JOB_STOPPED) then
256 job_id = shell%jobs(i)%job_id
257 exit
258 end if
259 end do
260
261 if (job_id == 0) then
262 write(error_unit, '(a)') 'bg: no stopped job'
263 shell%last_exit_status = 1
264 return
265 end if
266 else
267 ! Parse job number (handle %n syntax)
268 if (cmd%tokens(2)(1:1) == '%') then
269 read(cmd%tokens(2)(2:), *, iostat=iostat) job_id
270 else
271 read(cmd%tokens(2), *, iostat=iostat) job_id
272 end if
273
274 if (iostat /= 0) then
275 write(error_unit, '(a)') 'bg: invalid job id'
276 shell%last_exit_status = 1
277 return
278 end if
279 end if
280
281 call resume_job_bg(shell, job_id)
282 end subroutine
283
284 subroutine builtin_source(cmd, shell)
285 type(command_t), intent(in) :: cmd
286 type(shell_state_t), intent(inout) :: shell
287
288 character(len=1024) :: filename
289 logical :: file_exists
290
291 ! Check if filename provided
292 if (cmd%num_tokens < 2) then
293 write(error_unit, '(a)') 'source: usage: source filename [arguments...]'
294 shell%last_exit_status = 1
295 return
296 end if
297
298 filename = trim(cmd%tokens(2))
299
300 ! Check if file exists and is readable
301 inquire(file=filename, exist=file_exists)
302 if (.not. file_exists) then
303 write(error_unit, '(a)') 'source: ' // trim(filename) // ': No such file or directory'
304 shell%last_exit_status = 1
305 return
306 end if
307
308 ! Mark the shell to source this file on next main loop iteration
309 ! This avoids circular dependency issues
310 shell%source_file = filename
311 shell%should_source = .true.
312 shell%last_exit_status = 0
313
314 write(output_unit, '(a)') 'source: ' // trim(filename) // ' queued for execution'
315 end subroutine
316
317 subroutine builtin_history(cmd, shell)
318 type(command_t), intent(in) :: cmd
319 type(shell_state_t), intent(inout) :: shell
320
321 ! Handle history command options
322 if (cmd%num_tokens > 1) then
323 select case(trim(cmd%tokens(2)))
324 case('-c', '--clear')
325 call clear_history()
326 write(output_unit, '(a)') 'Command history cleared.'
327 case default
328 write(error_unit, '(a)') 'history: unknown option'
329 shell%last_exit_status = 1
330 return
331 end select
332 else
333 ! Show all history
334 call show_history()
335 end if
336
337 shell%last_exit_status = 0
338 end subroutine
339
340 subroutine builtin_kill(cmd, shell)
341 type(command_t), intent(in) :: cmd
342 type(shell_state_t), intent(inout) :: shell
343 integer :: signal_num, target_pid, iostat, ret
344 integer :: i, arg_start
345 logical :: found_signal
346
347 signal_num = 15 ! Default: SIGTERM
348 arg_start = 2
349 found_signal = .false.
350
351 if (cmd%num_tokens < 2) then
352 write(error_unit, '(a)') 'kill: usage: kill [-signal] pid...'
353 shell%last_exit_status = 1
354 return
355 end if
356
357 ! Check if first argument is a signal specifier or -l flag
358 if (cmd%tokens(2)(1:1) == '-') then
359 if (len_trim(cmd%tokens(2)) > 1) then
360 ! Check for -l flag (list signals)
361 if (trim(cmd%tokens(2)) == '-l') then
362 write(output_unit, '(a)') 'Available signals:'
363 write(output_unit, '(a)') ' 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL'
364 write(output_unit, '(a)') ' 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE'
365 write(output_unit, '(a)') ' 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2'
366 write(output_unit, '(a)') ' 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT'
367 write(output_unit, '(a)') ' 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP'
368 write(output_unit, '(a)') ' 21) SIGTTIN 22) SIGTTOU'
369 shell%last_exit_status = 0
370 return
371 end if
372
373 read(cmd%tokens(2)(2:), *, iostat=iostat) signal_num
374 if (iostat /= 0) then
375 ! Try named signals
376 select case(trim(cmd%tokens(2)(2:)))
377 case('TERM', 'term')
378 signal_num = 15
379 case('KILL', 'kill')
380 signal_num = 9
381 case('INT', 'int')
382 signal_num = 2
383 case('STOP', 'stop')
384 signal_num = 19
385 case('CONT', 'cont')
386 signal_num = 18
387 case('HUP', 'hup')
388 signal_num = 1
389 case('QUIT', 'quit')
390 signal_num = 3
391 case default
392 write(error_unit, '(a)') 'kill: invalid signal specification'
393 shell%last_exit_status = 1
394 return
395 end select
396 end if
397 found_signal = .true.
398 arg_start = 3
399 end if
400 end if
401
402 if (cmd%num_tokens < arg_start) then
403 write(error_unit, '(a)') 'kill: usage: kill [-signal] pid...'
404 shell%last_exit_status = 1
405 return
406 end if
407
408 ! Kill each specified process
409 do i = arg_start, cmd%num_tokens
410 ! Handle job syntax (%n)
411 if (cmd%tokens(i)(1:1) == '%') then
412 read(cmd%tokens(i)(2:), *, iostat=iostat) target_pid
413 if (iostat == 0) then
414 ! Find job by job_id and get its pgid
415 target_pid = find_job_pgid(shell, target_pid)
416 if (target_pid <= 0) then
417 write(error_unit, '(a)') 'kill: no such job'
418 shell%last_exit_status = 1
419 cycle
420 end if
421 target_pid = -target_pid ! Kill entire process group
422 else
423 write(error_unit, '(a)') 'kill: invalid job specification'
424 shell%last_exit_status = 1
425 cycle
426 end if
427 else
428 read(cmd%tokens(i), *, iostat=iostat) target_pid
429 if (iostat /= 0) then
430 write(error_unit, '(a)') 'kill: invalid pid'
431 shell%last_exit_status = 1
432 cycle
433 end if
434 end if
435
436 ret = c_kill(int(target_pid, c_pid_t), int(signal_num, c_int))
437 if (ret /= 0) then
438 write(error_unit, '(a,i0)') 'kill: failed to kill process ', target_pid
439 shell%last_exit_status = 1
440 end if
441 end do
442
443 if (shell%last_exit_status /= 1) then
444 shell%last_exit_status = 0
445 end if
446 end subroutine
447
448
449 subroutine builtin_wait(cmd, shell)
450 type(command_t), intent(in) :: cmd
451 type(shell_state_t), intent(inout) :: shell
452 integer :: target_pid, iostat, ret
453 integer(c_int), target :: wait_status
454 integer :: i
455
456 if (cmd%num_tokens == 1) then
457 ! Wait for all background jobs
458 do i = 1, MAX_JOBS
459 if (shell%jobs(i)%job_id > 0 .and. &
460 shell%jobs(i)%state == JOB_RUNNING) then
461 ret = c_waitpid(shell%jobs(i)%pgid, c_loc(wait_status), 0)
462 if (WIFEXITED(wait_status)) then
463 shell%jobs(i)%state = JOB_DONE
464 end if
465 end if
466 end do
467 else
468 ! Wait for specific job or PID
469 do i = 2, cmd%num_tokens
470 if (cmd%tokens(i)(1:1) == '%') then
471 ! Job syntax
472 read(cmd%tokens(i)(2:), *, iostat=iostat) target_pid
473 if (iostat == 0) then
474 target_pid = find_job_pgid(shell, target_pid)
475 else
476 write(error_unit, '(a)') 'wait: invalid job specification'
477 shell%last_exit_status = 1
478 cycle
479 end if
480 else
481 read(cmd%tokens(i), *, iostat=iostat) target_pid
482 if (iostat /= 0) then
483 write(error_unit, '(a)') 'wait: invalid pid'
484 shell%last_exit_status = 1
485 cycle
486 end if
487 end if
488
489 if (target_pid > 0) then
490 ret = c_waitpid(int(target_pid, c_pid_t), c_loc(wait_status), 0)
491 if (ret > 0) then
492 if (WIFEXITED(wait_status)) then
493 shell%last_exit_status = WEXITSTATUS(wait_status)
494 else
495 shell%last_exit_status = 1
496 end if
497 else
498 write(error_unit, '(a,i0)') 'wait: pid ', target_pid, ' is not a child of this shell'
499 shell%last_exit_status = 1
500 end if
501 end if
502 end do
503 end if
504
505 if (shell%last_exit_status /= 1) then
506 shell%last_exit_status = 0
507 end if
508 end subroutine
509
510 subroutine builtin_trap(cmd, shell)
511 type(command_t), intent(in) :: cmd
512 type(shell_state_t), intent(inout) :: shell
513
514 ! Simplified trap implementation - in a full shell this would
515 ! handle signal trap management
516 if (cmd%num_tokens < 3) then
517 write(error_unit, '(a)') 'trap: usage: trap action signal...'
518 shell%last_exit_status = 1
519 return
520 end if
521
522 ! For now, just acknowledge the trap command
523 write(output_unit, '(a)') 'trap: signal trapping not yet fully implemented'
524 shell%last_exit_status = 0
525 end subroutine
526
527 subroutine builtin_config(cmd, shell)
528 type(command_t), intent(in) :: cmd
529 type(shell_state_t), intent(inout) :: shell
530
531 if (cmd%num_tokens == 1) then
532 ! Show current config
533 call show_config()
534 else
535 select case(trim(cmd%tokens(2)))
536 case('show')
537 call show_config()
538 case('create')
539 call create_default_config()
540 case('reload')
541 call load_config_file(shell)
542 case default
543 write(error_unit, '(a)') 'config: usage: config [show|create|reload]'
544 shell%last_exit_status = 1
545 return
546 end select
547 end if
548
549 shell%last_exit_status = 0
550 end subroutine
551
552 subroutine builtin_alias(cmd, shell)
553 type(command_t), intent(in) :: cmd
554 type(shell_state_t), intent(inout) :: shell
555 integer :: eq_pos
556 character(len=256) :: alias_name, alias_command
557
558 if (cmd%num_tokens == 1) then
559 ! Show all aliases
560 call show_aliases(shell)
561 else if (cmd%num_tokens == 2) then
562 ! Check for alias=command format
563 eq_pos = index(cmd%tokens(2), '=')
564 if (eq_pos > 0) then
565 alias_name = cmd%tokens(2)(:eq_pos-1)
566 alias_command = cmd%tokens(2)(eq_pos+1:)
567
568 ! Remove quotes if present
569 if (alias_command(1:1) == '"' .and. alias_command(len_trim(alias_command):len_trim(alias_command)) == '"') then
570 alias_command = alias_command(2:len_trim(alias_command)-1)
571 else if (alias_command(1:1) == "'" .and. alias_command(len_trim(alias_command):len_trim(alias_command)) == "'") then
572 alias_command = alias_command(2:len_trim(alias_command)-1)
573 end if
574
575 call set_alias(shell, trim(alias_name), trim(alias_command))
576 else
577 ! Show specific alias
578 alias_name = cmd%tokens(2)
579 alias_command = get_alias(shell, trim(alias_name))
580 if (len(alias_command) > 0) then
581 write(output_unit, '(a)') 'alias ' // trim(alias_name) // &
582 '=' // "'" // trim(alias_command) // "'"
583 else
584 write(error_unit, '(a)') 'alias: ' // trim(alias_name) // ': not found'
585 shell%last_exit_status = 1
586 return
587 end if
588 end if
589 else
590 write(error_unit, '(a)') 'alias: usage: alias [name[=value]...]'
591 shell%last_exit_status = 1
592 return
593 end if
594
595 shell%last_exit_status = 0
596 end subroutine
597
598 subroutine builtin_unalias(cmd, shell)
599 type(command_t), intent(in) :: cmd
600 type(shell_state_t), intent(inout) :: shell
601 integer :: i
602
603 if (cmd%num_tokens < 2) then
604 write(error_unit, '(a)') 'unalias: usage: unalias name...'
605 shell%last_exit_status = 1
606 return
607 end if
608
609 ! Remove each specified alias
610 do i = 2, cmd%num_tokens
611 call unset_alias(shell, trim(cmd%tokens(i)))
612 end do
613
614 shell%last_exit_status = 0
615 end subroutine
616
617 subroutine builtin_help(cmd, shell)
618 type(command_t), intent(in) :: cmd
619 type(shell_state_t), intent(inout) :: shell
620
621 write(output_unit, '(a)') 'Fortran Shell (fortsh) - Built-in Commands:'
622 write(output_unit, '(a)') '========================================'
623 write(output_unit, '(a)') ''
624 write(output_unit, '(a)') 'Navigation & Files:'
625 write(output_unit, '(a)') ' cd [dir] - Change directory (use cd ~ for home)'
626 write(output_unit, '(a)') ' pwd - Print working directory'
627 write(output_unit, '(a)') ''
628 write(output_unit, '(a)') 'Variables & Environment:'
629 write(output_unit, '(a)') ' export VAR=val - Set environment variable'
630 write(output_unit, '(a)') ' echo [args] - Display text'
631 write(output_unit, '(a)') ''
632 write(output_unit, '(a)') 'Job Control:'
633 write(output_unit, '(a)') ' jobs - List active jobs'
634 write(output_unit, '(a)') ' fg [%n] - Bring job to foreground'
635 write(output_unit, '(a)') ' bg [%n] - Send job to background'
636 write(output_unit, '(a)') ' kill [-sig] pid - Send signal to process'
637 write(output_unit, '(a)') ' wait [pid] - Wait for process to complete'
638 write(output_unit, '(a)') ''
639 write(output_unit, '(a)') 'Shell Features:'
640 write(output_unit, '(a)') ' history - Show command history'
641 write(output_unit, '(a)') ' alias [n=cmd] - Create/show command aliases'
642 write(output_unit, '(a)') ' unalias name - Remove alias'
643 write(output_unit, '(a)') ' config [cmd] - Manage shell configuration (.fshrc)'
644 write(output_unit, '(a)') ''
645 write(output_unit, '(a)') 'Control Flow:'
646 write(output_unit, '(a)') ' test / [ ] - Evaluate conditions'
647 write(output_unit, '(a)') ' if/then/else/fi - Conditional execution'
648 write(output_unit, '(a)') ' while/do/done - Loop constructs'
649 write(output_unit, '(a)') ''
650 write(output_unit, '(a)') 'Other:'
651 write(output_unit, '(a)') ' source file - Execute script (not yet implemented)'
652 write(output_unit, '(a)') ' trap - Signal handling (basic support)'
653 write(output_unit, '(a)') ' rawtest - Test raw terminal input (interactive only)'
654 write(output_unit, '(a)') ' help - Show this help message'
655 write(output_unit, '(a)') ' exit [code] - Exit shell'
656 write(output_unit, '(a)') ''
657 write(output_unit, '(a)') 'Interactive Editing (available in interactive mode):'
658 write(output_unit, '(a)') ' ↑/↓ - Navigate command history'
659 write(output_unit, '(a)') ' ←/→, Ctrl+B/F - Move cursor left/right'
660 write(output_unit, '(a)') ' Ctrl+A - Move to beginning of line (Home)'
661 write(output_unit, '(a)') ' Ctrl+E - Move to end of line (End)'
662 write(output_unit, '(a)') ' Tab - Smart command/file completion'
663 write(output_unit, '(a)') ' Ctrl+K - Kill text to end of line'
664 write(output_unit, '(a)') ' Ctrl+U - Kill entire line'
665 write(output_unit, '(a)') ' Ctrl+W - Kill previous word'
666 write(output_unit, '(a)') ' Ctrl+Y - Yank (paste) killed text'
667 write(output_unit, '(a)') ' Ctrl+L - Clear screen'
668 write(output_unit, '(a)') ''
669 write(output_unit, '(a)') 'Features: Advanced readline, tab completion, history, aliases, job control'
670
671 shell%last_exit_status = 0
672 end subroutine
673
674 subroutine builtin_perf(cmd, shell)
675 type(command_t), intent(in) :: cmd
676 type(shell_state_t), intent(inout) :: shell
677
678 if (cmd%num_tokens > 1) then
679 select case(trim(cmd%tokens(2)))
680 case('on')
681 call set_performance_monitoring(.true.)
682 write(output_unit, '(a)') 'Performance monitoring enabled'
683 case('off')
684 call set_performance_monitoring(.false.)
685 write(output_unit, '(a)') 'Performance monitoring disabled'
686 case('stats', 'status')
687 call print_performance_stats()
688 case('reset')
689 total_commands = 0
690 total_parse_time = 0
691 total_exec_time = 0
692 total_glob_time = 0
693 write(output_unit, '(a)') 'Performance counters reset'
694 case default
695 write(error_unit, '(a)') 'perf: Usage: perf [on|off|stats|reset]'
696 shell%last_exit_status = 1
697 return
698 end select
699 else
700 ! Show current status
701 if (perf_monitoring_enabled) then
702 write(output_unit, '(a)') 'Performance monitoring: ENABLED'
703 else
704 write(output_unit, '(a)') 'Performance monitoring: DISABLED'
705 end if
706 write(output_unit, '(a,i0)') 'Commands processed: ', total_commands
707 write(output_unit, '(a,i0,a)') 'Memory usage: ', get_memory_usage(), ' KB'
708 end if
709
710 shell%last_exit_status = 0
711 end subroutine
712
713 subroutine builtin_memory(cmd, shell)
714 type(command_t), intent(in) :: cmd
715 type(shell_state_t), intent(inout) :: shell
716
717 if (cmd%num_tokens > 1) then
718 select case(trim(cmd%tokens(2)))
719 case('optimize')
720 call optimize_memory_pools()
721 write(output_unit, '(a)') 'Memory pools optimized'
722 case('stats')
723 call print_pool_stats()
724 case('auto')
725 call auto_optimize_memory()
726 write(output_unit, '(a)') 'Auto memory optimization triggered'
727 case default
728 write(error_unit, '(a)') 'memory: Usage: memory [optimize|stats|auto]'
729 shell%last_exit_status = 1
730 return
731 end select
732 else
733 ! Show memory status
734 write(output_unit, '(a)') 'Memory Usage Summary:'
735 write(output_unit, '(a)') '===================='
736 write(output_unit, '(a,i0)') 'Current allocations: ', current_allocations
737 write(output_unit, '(a,i0)') 'Peak allocations: ', peak_allocations
738 write(output_unit, '(a,i0,a)') 'Current memory: ', current_memory_used, ' bytes'
739 write(output_unit, '(a,i0,a)') 'Peak memory: ', peak_memory_used, ' bytes'
740
741 if (needs_memory_optimization()) then
742 write(output_unit, '(a)') ''
743 write(output_unit, '(a)') 'Tip: Memory optimization recommended. Run "memory optimize"'
744 end if
745 end if
746
747 shell%last_exit_status = 0
748 end subroutine
749
750 subroutine builtin_rawtest(cmd, shell)
751 type(command_t), intent(in) :: cmd
752 type(shell_state_t), intent(inout) :: shell
753 type(termios_t) :: original_termios
754 character :: ch
755 logical :: success
756 integer :: char_code
757
758 write(output_unit, '(a)') 'Raw mode test - press keys to see codes, q to quit:'
759 write(output_unit, '(a)') 'Entering raw mode...'
760
761 ! Enable raw mode
762 success = enable_raw_mode(original_termios)
763 if (.not. success) then
764 write(error_unit, '(a)') 'rawtest: Failed to enable raw mode'
765 shell%last_exit_status = 1
766 return
767 end if
768
769 ! Read characters until 'q' is pressed
770 do
771 success = read_single_char(ch)
772 if (.not. success) exit
773
774 char_code = iachar(ch)
775
776 ! Exit on 'q'
777 if (ch == 'q' .or. ch == 'Q') exit
778
779 ! Handle special characters
780 if (char_code == 27) then
781 ! Escape sequence - try to read more
782 write(output_unit, '(a)', advance='no') 'ESC '
783 success = read_single_char(ch)
784 if (success) then
785 write(output_unit, '(a,i0)', advance='no') '[', iachar(ch)
786 if (ch == '[') then
787 success = read_single_char(ch)
788 if (success) then
789 write(output_unit, '(a,i0,a)', advance='no') '[', iachar(ch), '] = '
790 select case(ch)
791 case('A')
792 write(output_unit, '(a)') 'UP ARROW'
793 case('B')
794 write(output_unit, '(a)') 'DOWN ARROW'
795 case('C')
796 write(output_unit, '(a)') 'RIGHT ARROW'
797 case('D')
798 write(output_unit, '(a)') 'LEFT ARROW'
799 case default
800 write(output_unit, '(a)') 'UNKNOWN ESCAPE'
801 end select
802 end if
803 else
804 write(output_unit, '(a)') '] = ALT+key'
805 end if
806 end if
807 else if (char_code < 32) then
808 ! Control character
809 write(output_unit, '(a,i0,a)') 'CTRL+', char_code, ' (^', char(char_code + 64), ')'
810 else if (char_code == 127) then
811 write(output_unit, '(a)') 'BACKSPACE/DELETE (127)'
812 else
813 ! Regular character
814 write(output_unit, '(a,a,a,i0,a)') 'Regular: ''', ch, ''' (', char_code, ')'
815 end if
816 end do
817
818 ! Restore terminal
819 success = restore_terminal(original_termios)
820 if (.not. success) then
821 write(error_unit, '(a)') 'rawtest: Warning - failed to restore terminal'
822 end if
823
824 write(output_unit, '(a)') ''
825 write(output_unit, '(a)') 'Raw mode test completed.'
826 shell%last_exit_status = 0
827 end subroutine
828
829 end module builtins