···1717 module J = Job.Make (E)
1818 module A = Arith.Make (S)
19192020- class default_map =
2121- object (_)
2222- inherit Ast.map
2323- method string (s : string) = s
2424- method int (i : int) = i
2525- method char c = c
2626- method option f v = Option.map f v
2727- method nlist__t f t = Nlist.map f t
2828- method nslist__t f t = Nslist.map f t
2929- method list f t = List.map f t
3030- end
3131-3220 type signal_handler = { run : (unit -> unit) -> unit; sigint_set : bool }
33213422 type ctx = {
···5341 exit_handler : (unit -> unit) option;
5442 }
55435656- let clear_local_state ctx = { ctx with local_state = [] }
4444+ let _stdin ctx = ctx.stdin
57455858- class default_ctx_fold =
5959- object (_)
6060- inherit [ctx] Ast.fold
6161- method int _ ctx = ctx
6262- method string _ ctx = ctx
6363- method char _ ctx = ctx
6464- method option f v ctx = Option.fold ~none:ctx ~some:(fun i -> f i ctx) v
6565- method nlist__t f v ctx = Nlist.fold_left (fun acc i -> f i acc) ctx v
6666-6767- method nslist__t f g v ctx =
6868- Nslist.fold_left (fun acc a b -> f a acc |> g b) ctx v
6969-7070- method list f v ctx = List.fold_left (fun acc i -> f i acc) ctx v
7171- end
7272-7373- let map_word_components f ast =
7474- let o =
7575- object (_)
7676- inherit default_map
7777- method! word_component cst = f cst
7878- end
7979- in
8080- o#complete_command ast
8181-8282- let map_words ?(skip_for_clauses = true) f =
8383- let o =
8484- object (_)
8585- inherit default_map as super
8686- method! word cst = f cst
4646+ let make_ctx ?(interactive = false) ?(subshell = false) ?(local_state = [])
4747+ ?(background_jobs = []) ?(last_background_process = "") ?(functions = [])
4848+ ?(rdrs = []) ?exit_handler ?(options = Built_ins.Options.default)
4949+ ?(hash = Hash.empty) ~fs ~stdin ~stdout ~async_switch ~program ~argv
5050+ ~signal_handler state executor =
5151+ let signal_handler = { run = signal_handler; sigint_set = false } in
5252+ {
5353+ interactive;
5454+ subshell;
5555+ state;
5656+ local_state;
5757+ executor;
5858+ fs;
5959+ options;
6060+ stdin;
6161+ stdout;
6262+ background_jobs;
6363+ last_background_process;
6464+ async_switch;
6565+ program;
6666+ argv;
6767+ functions;
6868+ hash;
6969+ rdrs;
7070+ signal_handler;
7171+ exit_handler;
7272+ }
87738888- method! for_clause cst =
8989- if skip_for_clauses then cst else super#for_clause cst
9090- end
9191- in
9292- o
7474+ let state ctx = ctx.state
7575+ let sigint_set ctx = ctx.signal_handler.sigint_set
7676+ let clear_local_state ctx = { ctx with local_state = [] }
93779478 let rec tilde_expansion ctx = function
9579 | [] -> []
···216200 | WordGlobAll | WordGlobAny -> true
217201 | _ -> false
218202219219- let apply_pair (a, b) f = f a b
220220- let ( ||> ) = apply_pair
221221-222203 let resolve_program ?(update = true) ctx name =
223204 let v =
224205 if not (String.contains name '/') then begin
···315296 (on_process ~async ctx, job)
316297 | Ok process ->
317298 let pgid = if Int.equal pgid 0 then E.pid process else pgid in
318318- let job =
319319- handle_job job (`Process process) |> fun j -> { j with id = pgid }
320320- in
299299+ let job = handle_job job (`Process process) |> J.set_id pgid in
321300 (on_process ~async ~process ctx, job)
322301 in
323323- let job_pgid (t : J.t) = t.id in
302302+ let job_pgid (t : J.t) = J.get_id t in
324303 let rec loop pipeline_switch (ctx : ctx) (job : J.t)
325304 (stdout_of_previous : Eio_unix.source_ty Eio_unix.source option) :
326305 Ast.command list -> ctx * J.t =
···567546 let subshell = saved_ctx.subshell || List.length p > 1 in
568547 let ctx = { initial_ctx with subshell } in
569548 let ctx, job = loop sw ctx initial_job None p in
570570- match job.processes with
571571- | [] -> Exit.zero ctx
572572- | _ :: _ ->
549549+ match J.size job with
550550+ | 0 -> Exit.zero ctx
551551+ | _ ->
573552 if not async then begin
574553 J.await_exit ~pipefail:ctx.options.pipefail
575554 ~interactive:ctx.interactive job
+43
src/lib/eval.mli
···11+open Eio.Std
22+(** Shell evaluation.
33+44+ This module provides the main evaluator for any shell. It requires users to
55+ provide a {! Types.State} module and a {! Types.Exec} module. *)
66+77+module Make (S : Types.State) (E : Types.Exec) : sig
88+ type ctx
99+ (** The context of the evaluation *)
1010+1111+ module J : Types.Job with type process = E.process
1212+1313+ val make_ctx :
1414+ ?interactive:bool ->
1515+ ?subshell:bool ->
1616+ ?local_state:(string * string) list ->
1717+ ?background_jobs:J.t list ->
1818+ ?last_background_process:string ->
1919+ ?functions:(string * Sast.compound_command) list ->
2020+ ?rdrs:Types.redirect list ->
2121+ ?exit_handler:(unit -> unit) ->
2222+ ?options:Built_ins.Options.t ->
2323+ ?hash:Hash.t ->
2424+ fs:Eio.Fs.dir_ty Eio.Path.t ->
2525+ stdin:Eio_unix.source_ty r ->
2626+ stdout:Eio_unix.sink_ty r ->
2727+ async_switch:Switch.t ->
2828+ program:string ->
2929+ argv:string array ->
3030+ signal_handler:((unit -> unit) -> unit) ->
3131+ S.t ->
3232+ E.t ->
3333+ ctx
3434+3535+ val state : ctx -> S.t
3636+ (** Return the current state of the context. *)
3737+3838+ val sigint_set : ctx -> bool
3939+ (** Has the signal SIGINT been set via a trap. *)
4040+4141+ val run : ctx Exit.t -> Ast.t -> ctx Exit.t * Ast.t list
4242+ (** [run ctx ast] evaluates [ast] using the initial [ctx]. *)
4343+end
+3-3
src/lib/interactive.ml
···1616 let default_prompt (ctx : Eval.ctx Exit.t) =
1717 let state =
1818 match ctx with
1919- | Exit.Zero ctx | Exit.Nonzero { value = ctx; _ } -> ctx.state
1919+ | Exit.Zero ctx | Exit.Nonzero { value = ctx; _ } -> Eval.state ctx
2020 in
2121 let pp_status ppf = function
2222 | Exit.Zero _ -> ()
···8585 Sys.set_signal Sys.sigint Sys.Signal_ignore;
8686 let rec loop (ctx : Eval.ctx Exit.t) =
8787 Option.iter (Fmt.epr "%s%!")
8888- (S.lookup (Exit.value ctx).state ~param:"PS1"
8888+ (S.lookup (Exit.value ctx |> Eval.state) ~param:"PS1"
8989 |> Option.map Ast.word_components_to_string);
9090 let p = prompt ctx in
9191 Fmt.pr "%s\r%!" p;
···102102 | Ctrl_c ->
103103 let c = Exit.value ctx in
104104 Eunix.Signals.(raise Interrupt);
105105- if c.signal_handler.sigint_set then loop (Exit.zero c)
105105+ if Eval.sigint_set c then loop (Exit.zero c)
106106 else begin
107107 Fmt.pr "\n%!";
108108 loop (Exit.nonzero c 130)
+8-4
src/lib/job.ml
···11module Make (E : Types.Exec) = struct
22+ type process = E.process
33+24 type t = {
33- state : [ `Running ];
45 reap : unit Eio.Promise.t * unit Eio.Promise.u;
56 id : int;
67 (* Process list is in reverse order *)
78 processes :
88- [ `Process of E.process
99+ [ `Process of process
910 | `Built_in of unit Exit.t
1011 | `Exit of unit Exit.t
1112 | `Rdr of unit Exit.t
···1314 list;
1415 }
15161717+ let get_id t = t.id
1818+ let set_id new_id t = { t with id = new_id }
1619 let get_reaper t = t.reap
17201818- let make ?(state = `Running) id processes =
2121+ let make id processes =
1922 let reap = Eio.Promise.create () in
2020- { state; id; processes; reap }
2323+ { id; processes; reap }
21242225 let add_process proc t =
2326 { t with processes = List.cons (`Process proc) t.processes }
···2831 let add_error b t = { t with processes = List.cons (`Error b) t.processes }
2932 let add_rdr b t = { t with processes = List.cons (`Rdr b) t.processes }
3033 let add_exit b t = { t with processes = List.cons (`Exit b) t.processes }
3434+ let size t = List.length t.processes
31353236 (* Section 2.9.2 https://pubs.opengroup.org/onlinepubs/9799919799/ *)
3337 let await_exit ~pipefail ~interactive t =
+1
src/lib/job.mli
···11+module Make (E : Types.Exec) : Types.Job with type process = E.process
+38
src/lib/types.ml
···90909191 val await : process -> unit Exit.t
9292end
9393+9494+module type Job = sig
9595+ type t
9696+ (** A job for job control *)
9797+9898+ type process
9999+100100+ val get_reaper : t -> unit Eio.Promise.t * unit Eio.Promise.u
101101+102102+ val make :
103103+ int ->
104104+ [ `Built_in of unit Exit.t
105105+ | `Error of int
106106+ | `Exit of unit Exit.t
107107+ | `Process of process
108108+ | `Rdr of unit Exit.t ]
109109+ list ->
110110+ t
111111+112112+ val get_id : t -> int
113113+ (** Get the ID of the job. *)
114114+115115+ val set_id : int -> t -> t
116116+ (** Set the ID of the job. *)
117117+118118+ val add_process : process -> t -> t
119119+ val add_built_in : unit Exit.t -> t -> t
120120+ val add_error : int -> t -> t
121121+ val add_rdr : unit Exit.t -> t -> t
122122+ val add_exit : unit Exit.t -> t -> t
123123+124124+ val size : t -> int
125125+ (** Number of processes in this job *)
126126+127127+ val await_exit : pipefail:bool -> interactive:bool -> t -> unit Exit.t
128128+ (** Given a job, [await_exit] will wait for the job to finish and return the
129129+ exit based on the various options passed in. *)
130130+end