Select the types of activity you want to include in your feed.
Initial async jobs
We implement background jobs, however, because of Eio's structured concurrency we cannot actually stop running the shell and end up waiting on all the processes in the background!
···55let sh ~command ~dump ~file env =
66 let executor = Merry_posix.Exec.{ mgr = env#process_mgr } in
77 let interactive = Option.is_none file && Option.is_none command in
88+ Eio.Switch.run @@ fun async_switch ->
89 let ctx =
910 C.
1011 {
···1819 fs = env#fs;
1920 options = Merry.Built_ins.Options.default;
2021 stdout = None;
2222+ async_switch;
2323+ background_jobs = [];
2124 }
2225 in
2326 match (file, command) with
+16-6
src/lib/ast.ml
···3838 | CST.SeparatorOp_Semicolon -> Semicolon
39394040and clist : ?sep:separator -> CST.clist -> clist =
4141- fun ?(sep = Nosep) x ->
4141+ fun ?(sep = Semicolon) x ->
4242 match x with
4343 | CList_CList_SeparatorOp_AndOr (a, b, c) ->
4444 let next_sep = separator_op b.value in
···135135 match x with
136136 | CompoundList_LineBreak_Term (_, b) ->
137137 let b = term b.value in
138138- (b, Nosep)
138138+ (b, Semicolon)
139139 | CompoundList_LineBreak_Term_Separator (_, b, c) ->
140140 let b = term b.value in
141141 let c = separator c.value in
···151151 Nslist.cons b c rest
152152 | Term_AndOr a ->
153153 let a = and_or a.value in
154154- Nslist.singleton Nosep a
154154+ Nslist.singleton Semicolon a
155155156156and for_clause : CST.for_clause -> for_clause =
157157 fun x ->
···508508 fun x ->
509509 match x with
510510 | Separator_SeparatorOp_LineBreak (a, _) -> separator_op a.value
511511- | Separator_NewLineList _ -> Nosep
511511+ | Separator_NewLineList _ -> Semicolon
512512513513and word : CST.word -> word = fun x -> match x with Word (_, b) -> word_cst b
514514and word_cst : CST.word_cst -> word_cst = fun v -> List.map word_component v
···719719 Yojson.Safe.pretty_print ppf yjs
720720end
721721722722-let of_string s = Morbig.parse_string "-" s |> of_program
722722+let of_string s =
723723+ let f = Morbig.parse_string "-" s in
724724+ (* Fmt.pr "MORBIG====\n%!"; *)
725725+ (* Morbig.JsonHelpers.save_as_json true Out_channel.stdout f; *)
726726+ (* Fmt.pr "MORBIG====\n%!"; *)
727727+ of_program f
723728724729let of_file path =
725730 let fname = Eio.Path.native_exn path in
726726- Eio.Path.load path |> Morbig.parse_string fname |> of_program
731731+732732+ let f = Eio.Path.load path |> Morbig.parse_string fname in
733733+ (* Fmt.pr "MORBIG====\n%!"; *)
734734+ (* Morbig.JsonHelpers.save_as_json true Out_channel.stdout f; *)
735735+ (* Fmt.pr "MORBIG====\n%!"; *)
736736+ of_program f
727737728738let rec word_component_to_string : word_component -> string = function
729739 | WordName s -> s
···1616 let name = Unix.getlogin () in
1717 let host = Unix.gethostname () in
1818 Fmt.str "%s@%s" name host
1919+2020+external tcsetpgrp : int -> int -> int = "caml_merry_tcsetpgrp"
2121+external setpgrp : int -> int -> int = "caml_merry_setpgid"
2222+2323+let make_process_group () = match setpgrp 0 0 with 0 -> () | n -> exit n
2424+2525+let background () =
2626+ let _pgrid = Unix.getpid () in
2727+ ()
+63-34
src/lib/eval.ml
···22 Copyright (c) 2025 The merry programmers. All rights reserved.
33 SPDX-License-Identifier: ISC
44 -----------------------------------------------------------------*)
55+open Eio.Std
56open Import
67open Exit.Syntax
78···3536 fs : Eio.Fs.dir_ty Eio.Path.t;
3637 options : Built_ins.Options.t;
3738 stdout : Eio_unix.sink_ty Eio.Flow.sink option;
3939+ background_jobs : J.t list;
4040+ async_switch : Eio.Switch.t;
3841 }
39424043 let clear_local_state ctx = { ctx with local_state = [] }
···202205 List.fold_left (fun acc (k, _) -> List.remove_assoc k acc) env extra
203206 |> List.append extra
204207205205- let rec execute_commands initial_ctx pipeline_switch p : ctx Exit.t =
208208+ let rec handle_pipeline ~async initial_ctx pipeline_switch p : ctx Exit.t =
209209+ let pipeline_switch =
210210+ if async then initial_ctx.async_switch else pipeline_switch
211211+ in
206212 let handle_job ~pgid j p =
207213 match (j, p) with
208214 | None, _ ->
209209- Option.some
210210- @@ J.make ~state:`Running ~bang:false pgid (Nlist.Singleton p)
215215+ Option.some @@ J.make ~state:`Running pgid (Nlist.Singleton p)
211216 | Some j, `Process p -> Option.some @@ J.add_process p j
212217 | Some j, `Built_in p -> Option.some @@ J.add_built_in p j
213218 in
···362367 in
363368 match job with
364369 | None -> Exit.zero ctx
365365- | Some job -> J.await_exit ~pipefail:false job >|= fun () -> ctx
370370+ | Some job ->
371371+ if not async then J.await_exit ~pipefail:false job >|= fun () -> ctx
372372+ else begin
373373+ Exit.zero { ctx with background_jobs = job :: ctx.background_jobs }
374374+ end
366375367376 and expand_cst (ctx : ctx) cst =
368377 let cst = tilde_expansion ctx cst in
···382391 expand_redirects (ctx, v :: acc) rest
383392 | s :: rest -> expand_redirects (ctx, s :: acc) rest
384393385385- and handle_single_pipeline ~sw ctx c =
394394+ and handle_and_or ~sw ~async ctx c =
386395 let pipeline = function
387396 | Ast.Pipeline p -> (Fun.id, p)
388397 | Ast.Pipeline_Bang p -> (Exit.not, p)
···396405 match exit_so_far with
397406 | Exit.Zero ctx ->
398407 let f, p = pipeline p in
399399- f @@ execute_commands ctx sw p
408408+ f @@ handle_pipeline ~async ctx sw p
400409 | v -> v)
401410 | Or, Nlist.Singleton (p, _) -> (
402411 match exit_so_far with
403412 | Exit.Zero _ as ctx -> ctx
404413 | _ ->
405414 let f, p = pipeline p in
406406- f @@ execute_commands ctx sw p)
415415+ f @@ handle_pipeline ~async ctx sw p)
407416 | Noand_or, Nlist.Singleton (p, _) ->
408417 let f, p = pipeline p in
409409- f @@ execute_commands ctx sw p
418418+ f @@ handle_pipeline ~async ctx sw p
410419 | Noand_or, Nlist.Cons ((p, next_sep), rest) ->
411420 let f, p = pipeline p in
412412- fold (next_sep, f (execute_commands ctx sw p)) rest
421421+ fold (next_sep, f (handle_pipeline ~async ctx sw p)) rest
413422 | And, Nlist.Cons ((p, next_sep), rest) -> (
414423 match exit_so_far with
415424 | Exit.Zero ctx ->
416425 let f, p = pipeline p in
417417- fold (next_sep, f (execute_commands ctx sw p)) rest
426426+ fold (next_sep, f (handle_pipeline ~async ctx sw p)) rest
418427 | Exit.Nonzero _ as v -> v)
419428 | Or, Nlist.Cons ((p, next_sep), rest) -> (
420429 match exit_so_far with
421430 | Exit.Zero _ as exit_so_far -> fold (next_sep, exit_so_far) rest
422431 | Exit.Nonzero _ ->
423432 let f, p = pipeline p in
424424- fold (next_sep, f (execute_commands ctx sw p)) rest)
433433+ fold (next_sep, f (handle_pipeline ~async ctx sw p)) rest)
425434 in
426435 fold (Noand_or, Exit.zero ctx) c
427436···550559 word_glob_expand ctx cst)
551560 swc
552561553553- and exec initial_ctx (ast : Ast.complete_command) =
554554- let command, _ = ast in
562562+ and exec initial_ctx ((command, sep) : Ast.complete_command) =
555563 let rec loop : Eio.Switch.t -> ctx -> Ast.clist -> ctx Exit.t =
556564 fun sw ctx -> function
557557- | Nlist.Singleton (c, _) -> handle_single_pipeline ~sw ctx c
558558- | Nlist.Cons ((c, (Semicolon | Nosep)), cs) -> (
559559- match handle_single_pipeline ~sw ctx c with
565565+ | Nlist.Singleton (c, sep) ->
566566+ let async =
567567+ match sep with Semicolon -> false | Ampersand -> true
568568+ in
569569+ handle_and_or ~sw ~async ctx c
570570+ | Nlist.Cons ((c, sep), cs) -> (
571571+ let async =
572572+ match sep with Semicolon -> false | Ampersand -> true
573573+ in
574574+ match handle_and_or ~sw ~async ctx c with
560575 | Exit.Zero ctx -> loop sw ctx cs
561576 | v -> v)
562562- | _ -> Fmt.failwith "Background tasks not implemented yet!"
563577 in
564564- Eio.Switch.run @@ fun sw -> loop sw initial_ctx command
578578+ match sep with
579579+ | Some Semicolon | None ->
580580+ Eio.Switch.run @@ fun sw -> loop sw initial_ctx command
581581+ | Some Ampersand ->
582582+ Fiber.fork ~sw:initial_ctx.async_switch (fun () ->
583583+ Fiber.yield ();
584584+ let _ : ctx Exit.t =
585585+ loop initial_ctx.async_switch initial_ctx command
586586+ in
587587+ ());
588588+ Exit.zero initial_ctx
565589566590 and execute ctx ast = exec ctx ast
567591568592 and run ctx ast =
593593+ (* Make the shell its own process group *)
594594+ Eunix.make_process_group ();
569595 let ctx, cs =
570570- List.fold_left
571571- (fun (ctx, cs) command ->
572572- let ctx = Exit.value ctx in
573573- let exit = execute ctx command in
574574- match exit with
575575- | Exit.Nonzero { exit_code; message; should_exit; _ } -> (
576576- Option.iter (Fmt.epr "%s\n%!") message;
577577- match
578578- ( should_exit.interactive,
579579- should_exit.non_interactive,
580580- ctx.interactive )
581581- with
582582- | `Yes, _, true | _, `Yes, false -> Stdlib.exit exit_code
583583- | _ -> (exit, ast :: cs))
584584- | Exit.Zero _ as ctx -> (ctx, ast :: cs))
585585- (ctx, []) ast
596596+ let rec loop_commands (ctx, cs) (c : Ast.complete_commands) =
597597+ match c with
598598+ | [] -> (ctx, cs)
599599+ | command :: commands -> (
600600+ let ctx = Exit.value ctx in
601601+ let exit = execute ctx command in
602602+ match exit with
603603+ | Exit.Nonzero { exit_code; message; should_exit; _ } -> (
604604+ Option.iter (Fmt.epr "%s\n%!") message;
605605+ match
606606+ ( should_exit.interactive,
607607+ should_exit.non_interactive,
608608+ ctx.interactive )
609609+ with
610610+ | `Yes, _, true | _, `Yes, false -> Stdlib.exit exit_code
611611+ | _ -> loop_commands (exit, c :: cs) commands)
612612+ | Exit.Zero _ as ctx -> loop_commands (ctx, c :: cs) commands)
613613+ in
614614+ loop_commands (ctx, []) ast
586615 in
587616 (ctx, List.rev cs)
588617end
+3-6
src/lib/job.ml
···44 type t = {
55 state : [ `Running ];
66 id : int;
77- bang : bool;
87 (* Process list is in reverse order *)
98 processes : [ `Process of E.process | `Built_in of unit Exit.t ] Nlist.t;
109 }
11101212- let make ?(state = `Running) ~bang id processes =
1313- { state; id; processes; bang }
1111+ let make ?(state = `Running) id processes = { state; id; processes }
14121513 let add_process proc t =
1614 { t with processes = Nlist.cons (`Process proc) t.processes }
···2119 (* Section 2.9.2 https://pubs.opengroup.org/onlinepubs/9799919799/ *)
2220 let await_exit ~pipefail t =
2321 let await = function `Process p -> E.await p | `Built_in b -> b in
2424- match (pipefail, t.bang) with
2525- | false, false -> await (Nlist.hd t.processes)
2626- | false, true -> await (Nlist.hd t.processes) |> Exit.not
2222+ match pipefail with
2323+ | false -> await (Nlist.hd t.processes)
2724 | _ -> Fmt.failwith "TODO: pipefail"
2825end
+25
src/lib/merry_stubs.c
···11+#include <caml/mlvalues.h>
22+#include <caml/memory.h>
33+#include <caml/alloc.h>
44+#include <caml/fail.h>
55+66+#include <unistd.h>
77+88+99+value caml_merry_tcsetpgrp(value v_fd, value v_pid_t) {
1010+ CAMLparam2(v_fd, v_pid_t);
1111+ int res;
1212+1313+ res = tcsetpgrp(Int_val(v_fd), Long_val(v_pid_t));
1414+1515+ CAMLreturn(Val_int(res));
1616+}
1717+1818+value caml_merry_setpgid(value v_fd, value v_pid_t) {
1919+ CAMLparam2(v_fd, v_pid_t);
2020+ int res;
2121+2222+ res = setpgid(Long_val(v_fd), Long_val(v_pid_t));
2323+2424+ CAMLreturn(Val_int(res));
2525+}
+1-1
src/lib/sast.ml
···109109 | IoHere_DLessDash_HereEnd of here_end * word
110110111111and here_end = HereEnd_Word of word
112112-and separator = Ampersand | Semicolon | Nosep
112112+and separator = Ampersand | Semicolon
113113and word = word_cst
114114and word_cst = word_component list
115115