Shells in OCaml

More type abstraction

+136 -89
+7 -25
src/bin/main.ml
··· 9 9 Eio.Switch.run @@ fun async_switch -> 10 10 let signal_handler f = Eio_posix.run @@ fun _ -> f () in 11 11 let ctx = 12 - C. 13 - { 14 - interactive; 15 - subshell = false; 16 - state = 17 - Merry_posix.State.make 18 - ~home:(Sys.getenv "HOME" ^ "/") 19 - (Fpath.v (Merry.Eunix.cwd ())); 20 - local_state = []; 21 - executor; 22 - fs = env#fs; 23 - options = Merry.Built_ins.Options.default; 24 - stdin = env#stdin; 25 - stdout = env#stdout; 26 - async_switch; 27 - background_jobs = []; 28 - last_background_process = ""; 29 - argv = Array.of_list (pos_zero :: rest); 30 - program = pos_zero; 31 - functions = []; 32 - hash = Merry.Hash.empty; 33 - rdrs = []; 34 - signal_handler = { run = signal_handler; sigint_set = false }; 35 - exit_handler = None; 36 - } 12 + C.make_ctx ~interactive 13 + (Merry_posix.State.make 14 + ~home:(Sys.getenv "HOME" ^ "/") 15 + (Fpath.v (Merry.Eunix.cwd ()))) 16 + executor ~fs:env#fs ~stdin:env#stdin ~stdout:env#stdout ~async_switch 17 + ~argv:(Array.of_list (pos_zero :: rest)) 18 + ~program:pos_zero ~signal_handler 37 19 in 38 20 match (file, command) with 39 21 | None, None -> I.run (Merry.Exit.zero ctx)
+36 -57
src/lib/eval.ml
··· 17 17 module J = Job.Make (E) 18 18 module A = Arith.Make (S) 19 19 20 - class default_map = 21 - object (_) 22 - inherit Ast.map 23 - method string (s : string) = s 24 - method int (i : int) = i 25 - method char c = c 26 - method option f v = Option.map f v 27 - method nlist__t f t = Nlist.map f t 28 - method nslist__t f t = Nslist.map f t 29 - method list f t = List.map f t 30 - end 31 - 32 20 type signal_handler = { run : (unit -> unit) -> unit; sigint_set : bool } 33 21 34 22 type ctx = { ··· 53 41 exit_handler : (unit -> unit) option; 54 42 } 55 43 56 - let clear_local_state ctx = { ctx with local_state = [] } 44 + let _stdin ctx = ctx.stdin 57 45 58 - class default_ctx_fold = 59 - object (_) 60 - inherit [ctx] Ast.fold 61 - method int _ ctx = ctx 62 - method string _ ctx = ctx 63 - method char _ ctx = ctx 64 - method option f v ctx = Option.fold ~none:ctx ~some:(fun i -> f i ctx) v 65 - method nlist__t f v ctx = Nlist.fold_left (fun acc i -> f i acc) ctx v 66 - 67 - method nslist__t f g v ctx = 68 - Nslist.fold_left (fun acc a b -> f a acc |> g b) ctx v 69 - 70 - method list f v ctx = List.fold_left (fun acc i -> f i acc) ctx v 71 - end 72 - 73 - let map_word_components f ast = 74 - let o = 75 - object (_) 76 - inherit default_map 77 - method! word_component cst = f cst 78 - end 79 - in 80 - o#complete_command ast 81 - 82 - let map_words ?(skip_for_clauses = true) f = 83 - let o = 84 - object (_) 85 - inherit default_map as super 86 - method! word cst = f cst 46 + let make_ctx ?(interactive = false) ?(subshell = false) ?(local_state = []) 47 + ?(background_jobs = []) ?(last_background_process = "") ?(functions = []) 48 + ?(rdrs = []) ?exit_handler ?(options = Built_ins.Options.default) 49 + ?(hash = Hash.empty) ~fs ~stdin ~stdout ~async_switch ~program ~argv 50 + ~signal_handler state executor = 51 + let signal_handler = { run = signal_handler; sigint_set = false } in 52 + { 53 + interactive; 54 + subshell; 55 + state; 56 + local_state; 57 + executor; 58 + fs; 59 + options; 60 + stdin; 61 + stdout; 62 + background_jobs; 63 + last_background_process; 64 + async_switch; 65 + program; 66 + argv; 67 + functions; 68 + hash; 69 + rdrs; 70 + signal_handler; 71 + exit_handler; 72 + } 87 73 88 - method! for_clause cst = 89 - if skip_for_clauses then cst else super#for_clause cst 90 - end 91 - in 92 - o 74 + let state ctx = ctx.state 75 + let sigint_set ctx = ctx.signal_handler.sigint_set 76 + let clear_local_state ctx = { ctx with local_state = [] } 93 77 94 78 let rec tilde_expansion ctx = function 95 79 | [] -> [] ··· 216 200 | WordGlobAll | WordGlobAny -> true 217 201 | _ -> false 218 202 219 - let apply_pair (a, b) f = f a b 220 - let ( ||> ) = apply_pair 221 - 222 203 let resolve_program ?(update = true) ctx name = 223 204 let v = 224 205 if not (String.contains name '/') then begin ··· 315 296 (on_process ~async ctx, job) 316 297 | Ok process -> 317 298 let pgid = if Int.equal pgid 0 then E.pid process else pgid in 318 - let job = 319 - handle_job job (`Process process) |> fun j -> { j with id = pgid } 320 - in 299 + let job = handle_job job (`Process process) |> J.set_id pgid in 321 300 (on_process ~async ~process ctx, job) 322 301 in 323 - let job_pgid (t : J.t) = t.id in 302 + let job_pgid (t : J.t) = J.get_id t in 324 303 let rec loop pipeline_switch (ctx : ctx) (job : J.t) 325 304 (stdout_of_previous : Eio_unix.source_ty Eio_unix.source option) : 326 305 Ast.command list -> ctx * J.t = ··· 567 546 let subshell = saved_ctx.subshell || List.length p > 1 in 568 547 let ctx = { initial_ctx with subshell } in 569 548 let ctx, job = loop sw ctx initial_job None p in 570 - match job.processes with 571 - | [] -> Exit.zero ctx 572 - | _ :: _ -> 549 + match J.size job with 550 + | 0 -> Exit.zero ctx 551 + | _ -> 573 552 if not async then begin 574 553 J.await_exit ~pipefail:ctx.options.pipefail 575 554 ~interactive:ctx.interactive job
+43
src/lib/eval.mli
··· 1 + open Eio.Std 2 + (** Shell evaluation. 3 + 4 + This module provides the main evaluator for any shell. It requires users to 5 + provide a {! Types.State} module and a {! Types.Exec} module. *) 6 + 7 + module Make (S : Types.State) (E : Types.Exec) : sig 8 + type ctx 9 + (** The context of the evaluation *) 10 + 11 + module J : Types.Job with type process = E.process 12 + 13 + val make_ctx : 14 + ?interactive:bool -> 15 + ?subshell:bool -> 16 + ?local_state:(string * string) list -> 17 + ?background_jobs:J.t list -> 18 + ?last_background_process:string -> 19 + ?functions:(string * Sast.compound_command) list -> 20 + ?rdrs:Types.redirect list -> 21 + ?exit_handler:(unit -> unit) -> 22 + ?options:Built_ins.Options.t -> 23 + ?hash:Hash.t -> 24 + fs:Eio.Fs.dir_ty Eio.Path.t -> 25 + stdin:Eio_unix.source_ty r -> 26 + stdout:Eio_unix.sink_ty r -> 27 + async_switch:Switch.t -> 28 + program:string -> 29 + argv:string array -> 30 + signal_handler:((unit -> unit) -> unit) -> 31 + S.t -> 32 + E.t -> 33 + ctx 34 + 35 + val state : ctx -> S.t 36 + (** Return the current state of the context. *) 37 + 38 + val sigint_set : ctx -> bool 39 + (** Has the signal SIGINT been set via a trap. *) 40 + 41 + val run : ctx Exit.t -> Ast.t -> ctx Exit.t * Ast.t list 42 + (** [run ctx ast] evaluates [ast] using the initial [ctx]. *) 43 + end
+3 -3
src/lib/interactive.ml
··· 16 16 let default_prompt (ctx : Eval.ctx Exit.t) = 17 17 let state = 18 18 match ctx with 19 - | Exit.Zero ctx | Exit.Nonzero { value = ctx; _ } -> ctx.state 19 + | Exit.Zero ctx | Exit.Nonzero { value = ctx; _ } -> Eval.state ctx 20 20 in 21 21 let pp_status ppf = function 22 22 | Exit.Zero _ -> () ··· 85 85 Sys.set_signal Sys.sigint Sys.Signal_ignore; 86 86 let rec loop (ctx : Eval.ctx Exit.t) = 87 87 Option.iter (Fmt.epr "%s%!") 88 - (S.lookup (Exit.value ctx).state ~param:"PS1" 88 + (S.lookup (Exit.value ctx |> Eval.state) ~param:"PS1" 89 89 |> Option.map Ast.word_components_to_string); 90 90 let p = prompt ctx in 91 91 Fmt.pr "%s\r%!" p; ··· 102 102 | Ctrl_c -> 103 103 let c = Exit.value ctx in 104 104 Eunix.Signals.(raise Interrupt); 105 - if c.signal_handler.sigint_set then loop (Exit.zero c) 105 + if Eval.sigint_set c then loop (Exit.zero c) 106 106 else begin 107 107 Fmt.pr "\n%!"; 108 108 loop (Exit.nonzero c 130)
+8 -4
src/lib/job.ml
··· 1 1 module Make (E : Types.Exec) = struct 2 + type process = E.process 3 + 2 4 type t = { 3 - state : [ `Running ]; 4 5 reap : unit Eio.Promise.t * unit Eio.Promise.u; 5 6 id : int; 6 7 (* Process list is in reverse order *) 7 8 processes : 8 - [ `Process of E.process 9 + [ `Process of process 9 10 | `Built_in of unit Exit.t 10 11 | `Exit of unit Exit.t 11 12 | `Rdr of unit Exit.t ··· 13 14 list; 14 15 } 15 16 17 + let get_id t = t.id 18 + let set_id new_id t = { t with id = new_id } 16 19 let get_reaper t = t.reap 17 20 18 - let make ?(state = `Running) id processes = 21 + let make id processes = 19 22 let reap = Eio.Promise.create () in 20 - { state; id; processes; reap } 23 + { id; processes; reap } 21 24 22 25 let add_process proc t = 23 26 { t with processes = List.cons (`Process proc) t.processes } ··· 28 31 let add_error b t = { t with processes = List.cons (`Error b) t.processes } 29 32 let add_rdr b t = { t with processes = List.cons (`Rdr b) t.processes } 30 33 let add_exit b t = { t with processes = List.cons (`Exit b) t.processes } 34 + let size t = List.length t.processes 31 35 32 36 (* Section 2.9.2 https://pubs.opengroup.org/onlinepubs/9799919799/ *) 33 37 let await_exit ~pipefail ~interactive t =
+1
src/lib/job.mli
··· 1 + module Make (E : Types.Exec) : Types.Job with type process = E.process
+38
src/lib/types.ml
··· 90 90 91 91 val await : process -> unit Exit.t 92 92 end 93 + 94 + module type Job = sig 95 + type t 96 + (** A job for job control *) 97 + 98 + type process 99 + 100 + val get_reaper : t -> unit Eio.Promise.t * unit Eio.Promise.u 101 + 102 + val make : 103 + int -> 104 + [ `Built_in of unit Exit.t 105 + | `Error of int 106 + | `Exit of unit Exit.t 107 + | `Process of process 108 + | `Rdr of unit Exit.t ] 109 + list -> 110 + t 111 + 112 + val get_id : t -> int 113 + (** Get the ID of the job. *) 114 + 115 + val set_id : int -> t -> t 116 + (** Set the ID of the job. *) 117 + 118 + val add_process : process -> t -> t 119 + val add_built_in : unit Exit.t -> t -> t 120 + val add_error : int -> t -> t 121 + val add_rdr : unit Exit.t -> t -> t 122 + val add_exit : unit Exit.t -> t -> t 123 + 124 + val size : t -> int 125 + (** Number of processes in this job *) 126 + 127 + val await_exit : pipefail:bool -> interactive:bool -> t -> unit Exit.t 128 + (** Given a job, [await_exit] will wait for the job to finish and return the 129 + exit based on the various options passed in. *) 130 + end