diff --git a/TODO.md b/TODO.md index 584929b..85ef4a4 100644 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,10 @@ - Re-entrancy w.r.t to the file descriptor table and yielding fibers. - Non-switched async tasks (for now async mode is considered broken and off by default!) + +### Word Expansion + + - Morbig seems to be removing a trailing slash for us w.r.t tilde expansion -- + might need some thought: `HOME=/home/bactrian/; echo ~/documents` for + example. + - diff --git a/src/lib/built_ins.ml b/src/lib/built_ins.ml index 9378f15..74b3d8c 100644 --- a/src/lib/built_ins.ml +++ b/src/lib/built_ins.ml @@ -53,7 +53,7 @@ module Options = struct let pp ppf opt = let pp_option ppf (name, value) = - Fmt.pf ppf "%-12s %s@." name (if value then "on" else "off") + Fmt.pf ppf "%-12s %s\n" name (if value then "on" else "off") in let opts = let { noclobber; pipefail; async; no_path_expansion; no_unset; errexit } = @@ -68,7 +68,7 @@ module Options = struct ("async", async); ] in - Fmt.pf ppf "@[%a@]" Fmt.(list pp_option) opts + Fmt.pf ppf "@[%a@]" Fmt.(list ~sep:nop pp_option) opts end type set = { update : (Options.option * bool) list; print_options : bool } @@ -253,7 +253,9 @@ module Set = struct unset in let update = update @ unset in - Set { update; print_options = false } + match update with + | [] -> Set { update = []; print_options = true } + | update -> Set { update; print_options = false } in let term = Term.( diff --git a/src/lib/eval.ml b/src/lib/eval.ml index c0fee22..7c4df59 100644 --- a/src/lib/eval.ml +++ b/src/lib/eval.ml @@ -442,6 +442,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct | ":" -> job | _ -> ( (* TODO: Make concurrent *) + let saved_ctx = ctx in let func_app = if is_command then None else @@ -451,6 +452,12 @@ module Make (S : Types.State) (E : Types.Exec) = struct in match func_app with | Some ctx -> + let ctx = + Exit.map + ~f:(fun ctx -> + { saved_ctx with state = ctx.state }) + ctx + in close_flow ~is_global:is_stdout_global some_write; (* TODO: Proper job stuff and redirects etc. *) @@ -628,7 +635,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct rdrs = rdrs @ saved_rdrs; stdout = some_write; stdin = pctx.stdin; - subshell = true; + (* subshell = true; *) current_context = `CompoundCommand; } in @@ -820,7 +827,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct Fmt.epr "msh: cannot overwrite existing file\n%!"; Error ctx - and parameter_expansion ctx ast : ctx Exit.t * Ast.fragment list list = + and parameter_expansion ctx ast : ctx Exit.t * Ast.fragment list = let get_prefix ~pattern ~kind param = let _, prefix = String.fold_left @@ -868,11 +875,11 @@ module Make (S : Types.State) (E : Types.Exec) = struct f "lookup %s => %a" param Fmt.(quote (option string)) v); v in - let expand ctx v : ctx Exit.t * Ast.fragment list list = + let expand ctx v : ctx Exit.t * Ast.fragment list = let module Fragment = struct include Ast.Fragment - let make ?(join = if ctx.in_double_quotes then `With_previous else `No) + let make ?(join = if ctx.in_double_quotes then `With_previous else `Yes) ?globbable ?splittable ?tilde_expansion v = Ast.Fragment.make ~join ?splittable ?tilde_expansion ?globbable v end in @@ -880,22 +887,19 @@ module Make (S : Types.State) (E : Types.Exec) = struct | Ast.WordVariable v -> ( match v with | Ast.VariableAtom ("!", NoAttribute) -> - (Exit.zero ctx, [ [ Fragment.make ctx.last_background_process ] ]) + (Exit.zero ctx, [ Fragment.make ctx.last_background_process ]) | Ast.VariableAtom ("?", NoAttribute) -> let status = match ctx.last_pipeline_status with | None -> [] | Some i -> [ Fragment.make (string_of_int i) ] in - (Exit.zero ctx, [ status ]) + (Exit.zero ctx, status) | Ast.VariableAtom ("-", NoAttribute) -> let i = if ctx.interactive then "i" else "" in ( Exit.zero ctx, - [ - [ - Fragment.make (Built_ins.Options.to_letters ctx.options ^ i); - ]; - ] ) + [ Fragment.make (Built_ins.Options.to_letters ctx.options ^ i) ] + ) | Ast.VariableAtom ("@", NoAttribute) -> let args = tl_or_empty @@ Array.to_list ctx.argv in Debug.Log.debug (fun f -> @@ -903,16 +907,16 @@ module Make (S : Types.State) (E : Types.Exec) = struct let args = if not ctx.in_double_quotes then List.map - (fun v -> [ Fragment.make ~join:`No ~splittable:true v ]) + (fun v -> Fragment.make ~join:`No ~splittable:true v) args else let l = List.length args in List.mapi (fun idx arg -> - if idx = 0 then [ Fragment.make ~join:`With_previous arg ] + if idx = 0 then Fragment.make ~join:`With_previous arg else if idx = l - 1 then - [ Fragment.make ~join:`With_next arg ] - else [ Fragment.make ~join:`No arg ]) + Fragment.make ~join:`With_next arg + else Fragment.make ~join:`No arg) args in (Exit.zero ctx, args) @@ -921,75 +925,66 @@ module Make (S : Types.State) (E : Types.Exec) = struct Debug.Log.debug (fun f -> f "expanding *: %a\n%!" Fmt.(list string) args); let args = - if not ctx.in_double_quotes then - [ List.map Ast.Fragment.make args ] + if not ctx.in_double_quotes then List.map Ast.Fragment.make args else [ - [ - Ast.Fragment.make ~join:`With_previous - (String.concat - (Option.value ~default:" " - (S.lookup ctx.state ~param:"IFS")) - args); - ]; + Ast.Fragment.make ~join:`With_previous + (String.concat + (Option.value ~default:" " + (S.lookup ctx.state ~param:"IFS")) + args); ] in (Exit.zero ctx, args) | Ast.VariableAtom ("#", NoAttribute) -> ( Exit.zero ctx, [ - [ - Fragment.make - (string_of_int - (List.length @@ tl_or_empty (Array.to_list ctx.argv))); - ]; + Fragment.make + (string_of_int + (List.length @@ tl_or_empty (Array.to_list ctx.argv))); ] ) | Ast.VariableAtom (n, NoAttribute) when Option.is_some (int_of_string_opt n) -> ( let n = int_of_string n in match Array.get ctx.argv n with - | v -> (Exit.zero ctx, [ [ Fragment.make v ] ]) + | v -> (Exit.zero ctx, [ Fragment.make v ]) | exception Invalid_argument _ -> - (Exit.zero ctx, [ [ Fragment.make "" ] ])) + (Exit.zero ctx, [ Fragment.make "" ])) | Ast.VariableAtom (s, NoAttribute) -> ( match lookup_variable ctx ~param:s with | None -> if ctx.options.no_unset then begin ( Exit.nonzero_msg ctx ~exit_code:1 "%s: unbound variable" s, - [ [ Fragment.make "" ] ] ) + [ Fragment.make "" ] ) end - else (Exit.zero ctx, [ [ Fragment.make "" ] ]) + else (Exit.zero ctx, [ Fragment.make "" ]) | Some cst -> ( Exit.zero ctx, - [ - [ - Fragment.make ~splittable:(not ctx.in_double_quotes) cst; - ]; - ] )) + [ Fragment.make ~splittable:(not ctx.in_double_quotes) cst ] + )) | Ast.VariableAtom (s, ParameterLength) -> ( match lookup_variable ctx ~param:s with - | None -> (Exit.zero ctx, [ [ Fragment.make "0" ] ]) + | None -> (Exit.zero ctx, [ Fragment.make "0" ]) | Some cst -> ( Exit.zero ctx, - [ [ Fragment.make (string_of_int (String.length cst)) ] ] )) + [ Fragment.make (string_of_int (String.length cst)) ] )) | Ast.VariableAtom (s, UseDefaultValues (_, cst)) -> ( match lookup_variable ctx ~param:s with | None -> - (Exit.zero ctx, [ [ Fragment.make (word_cst_to_string cst) ] ]) - | Some cst -> (Exit.zero ctx, [ [ Fragment.make cst ] ])) + (Exit.zero ctx, [ Fragment.make (word_cst_to_string cst) ]) + | Some cst -> (Exit.zero ctx, [ Fragment.make cst ])) | Ast.VariableAtom ( s, (( RemoveSmallestPrefixPattern cst | RemoveLargestPrefixPattern cst ) as v) ) -> ( let ctx, spp = word_expansion ctx cst in match ctx with - | Exit.Nonzero _ as ctx -> (ctx, [ [ Fragment.make "" ] ]) + | Exit.Nonzero _ as ctx -> (ctx, [ Fragment.make "" ]) | Exit.Zero ctx -> ( let pattern = Fragment.join_list ~sep:"" (List.concat spp) in match lookup_variable ctx ~param:s with | None -> - ( Exit.zero ctx, - [ [ Fragment.make (word_cst_to_string cst) ] ] ) + (Exit.zero ctx, [ Fragment.make (word_cst_to_string cst) ]) | Some cst -> ( let kind = match v with @@ -1000,12 +995,11 @@ module Make (S : Types.State) (E : Types.Exec) = struct let param = cst in let prefix = get_prefix ~pattern ~kind param in match prefix with - | None -> (Exit.zero ctx, [ [ Fragment.make param ] ]) + | None -> (Exit.zero ctx, [ Fragment.make param ]) | Some s -> ( match String.cut_prefix ~prefix:s param with - | Some s -> (Exit.zero ctx, [ [ Fragment.make s ] ]) - | None -> (Exit.zero ctx, [ [ Fragment.make param ] ]) - )))) + | Some s -> (Exit.zero ctx, [ Fragment.make s ]) + | None -> (Exit.zero ctx, [ Fragment.make param ]))))) | Ast.VariableAtom ( s, (( RemoveSmallestSuffixPattern cst @@ -1013,12 +1007,11 @@ module Make (S : Types.State) (E : Types.Exec) = struct let ctx, spp = word_expansion ctx cst in let pattern = Fragment.join_list ~sep:"" (List.concat spp) in match ctx with - | Exit.Nonzero _ as ctx -> (ctx, [ [ Fragment.empty ] ]) + | Exit.Nonzero _ as ctx -> (ctx, [ Fragment.empty ]) | Exit.Zero ctx -> ( match lookup_variable ctx ~param:s with | None -> - ( Exit.zero ctx, - [ [ Fragment.make (word_cst_to_string cst) ] ] ) + (Exit.zero ctx, [ Fragment.make (word_cst_to_string cst) ]) | Some cst -> ( let kind = match v with @@ -1029,17 +1022,16 @@ module Make (S : Types.State) (E : Types.Exec) = struct let param = cst in let suffix = get_suffix ~pattern ~kind param in match suffix with - | None -> (Exit.zero ctx, [ [ Fragment.make param ] ]) + | None -> (Exit.zero ctx, [ Fragment.make param ]) | Some s -> ( match String.cut_suffix ~suffix:s param with - | Some s -> (Exit.zero ctx, [ [ Fragment.make s ] ]) - | None -> (Exit.zero ctx, [ [ Fragment.make param ] ]) - )))) + | Some s -> (Exit.zero ctx, [ Fragment.make s ]) + | None -> (Exit.zero ctx, [ Fragment.make param ]))))) | Ast.VariableAtom (s, UseAlternativeValue (_, alt)) -> ( let ctx, alt = word_expansion ctx alt in match lookup_variable (Exit.value ctx) ~param:s with - | Some "" | None -> (ctx, [ [] ]) - | Some _ -> (ctx, alt)) + | Some "" | None -> (ctx, []) + | Some _ -> (ctx, List.concat alt)) | Ast.VariableAtom (s, AssignDefaultValues (_, value)) -> ( let new_ctx, value = word_expansion ctx value in match lookup_variable (Exit.value new_ctx) ~param:s with @@ -1050,13 +1042,12 @@ module Make (S : Types.State) (E : Types.Exec) = struct with | Ok state -> let new_ctx = { (Exit.value new_ctx) with state } in - (Exit.zero new_ctx, value) - | Error m -> (Exit.nonzero_msg ~exit_code:1 ctx "%s" m, [ [] ]) - ) - | Some cst -> (new_ctx, [ [ Fragment.make cst ] ])) + (Exit.zero new_ctx, List.concat value) + | Error m -> (Exit.nonzero_msg ~exit_code:1 ctx "%s" m, [])) + | Some cst -> (new_ctx, [ Fragment.make cst ])) | Ast.VariableAtom (_, IndicateErrorifNullorUnset (_, _)) -> Fmt.failwith "TODO: Indicate Error") - | Ast.WordDoubleQuoted [] -> (Exit.zero ctx, [ [ Ast.Fragment.empty ] ]) + | Ast.WordDoubleQuoted [] -> (Exit.zero ctx, [ Ast.Fragment.empty ]) | Ast.WordDoubleQuoted cst -> ( let saved_dqoute = ctx.in_double_quotes in let ctx = { ctx with in_double_quotes = true } in @@ -1067,9 +1058,9 @@ module Make (S : Types.State) (E : Types.Exec) = struct new_ctx in match new_ctx with - | Exit.Nonzero _ -> (new_ctx, cst_acc) - | Exit.Zero new_ctx -> (Exit.zero new_ctx, cst_acc)) - | Ast.WordSingleQuoted [] -> (Exit.zero ctx, [ [ Ast.Fragment.empty ] ]) + | Exit.Nonzero _ -> (new_ctx, List.concat cst_acc) + | Exit.Zero new_ctx -> (Exit.zero new_ctx, List.concat cst_acc)) + | Ast.WordSingleQuoted [] -> (Exit.zero ctx, [ Ast.Fragment.empty ]) | Ast.WordSingleQuoted cst -> ( let saved_dqoute = ctx.in_double_quotes in let new_ctx, cst_acc = word_expansion ctx cst in @@ -1079,38 +1070,33 @@ module Make (S : Types.State) (E : Types.Exec) = struct new_ctx in match new_ctx with - | Exit.Nonzero _ -> (new_ctx, cst_acc) - | Exit.Zero new_ctx -> (Exit.zero new_ctx, cst_acc)) + | Exit.Nonzero _ -> (new_ctx, List.concat cst_acc) + | Exit.Zero new_ctx -> (Exit.zero new_ctx, List.concat cst_acc)) | Ast.WordAssignmentWord (Name n, w) -> ( let new_ctx, cst_acc = word_expansion ctx w in match new_ctx with - | Exit.Nonzero _ -> (new_ctx, cst_acc) + | Exit.Nonzero _ -> (new_ctx, List.concat cst_acc) | Exit.Zero _ -> ( new_ctx, [ - [ - Fragment.make - (n ^ "=" - ^ Fragment.join_list ~sep:"" (List.concat cst_acc)); - ]; + Fragment.make + (n ^ "=" ^ Fragment.join_list ~sep:"" (List.concat cst_acc)); ] )) | Ast.WordSubshell sub -> (* Command substitution *) let s = command_substitution ctx sub in - (Exit.zero ctx, [ [ Fragment.make ~join:`Yes s ] ]) + (Exit.zero ctx, [ Fragment.make ~join:`Yes s ]) | Ast.WordArithmeticExpression cst -> arithmetic_expansion ctx cst |> fun (ctx, v) -> - (Exit.zero ctx, [ [ Fragment.make @@ string_of_int v ] ]) - | Ast.WordName s -> (Exit.zero ctx, [ [ Fragment.make s ] ]) + (Exit.zero ctx, [ Fragment.make @@ string_of_int v ]) + | Ast.WordName s -> (Exit.zero ctx, [ Fragment.make s ]) | Ast.WordLiteral s -> let v = Fragment.make s in - (Exit.zero ctx, [ [ v ] ]) - | Ast.WordGlobAll -> - (Exit.zero ctx, [ [ Fragment.make ~globbable:true "*" ] ]) - | Ast.WordGlobAny -> - (Exit.zero ctx, [ [ Fragment.make ~globbable:true "?" ] ]) + (Exit.zero ctx, [ v ]) + | Ast.WordGlobAll -> (Exit.zero ctx, [ Fragment.make ~globbable:true "*" ]) + | Ast.WordGlobAny -> (Exit.zero ctx, [ Fragment.make ~globbable:true "?" ]) | Ast.WordTildePrefix s -> - (Exit.zero ctx, [ [ Fragment.make ~tilde_expansion:true s ] ]) + (Exit.zero ctx, [ Fragment.make ~tilde_expansion:true s ]) | v -> Fmt.failwith "TODO: expansion of %a" yojson_pp (Ast.word_component_to_yojson v) @@ -1131,14 +1117,14 @@ module Make (S : Types.State) (E : Types.Exec) = struct | [] -> [] | Ast.{ splittable = true; txt; globbable; _ } :: rest -> ( match S.lookup ctx.state ~param:"IFS" with - | Some "" -> [ Ast.Fragment.make ~globbable txt ] + | Some "" -> [ Ast.Fragment.make ~join:`No ~globbable txt ] | (None | Some _) as ifs -> let ifs = Option.value ~default:" \t\n" ifs in (split_fields ifs txt |> List.map (Ast.Fragment.make ~globbable)) @ field_splitting ctx rest) | txt :: rest -> txt :: field_splitting ctx rest - and word_expansion' ctx cst : ctx Exit.t * Ast.fragments list = + and word_expansion' ctx cst : ctx Exit.t * Ast.fragments = let cst = tilde_expansion ctx cst in parameter_expansion ctx cst @@ -1152,26 +1138,25 @@ module Make (S : Types.State) (E : Types.Exec) = struct (next_ctx, combined) in let ctx, cst = aux (Exit.zero ctx) cst in + let cst = Ast.Fragment.handle_joins cst in match ctx with - | Exit.Nonzero _ -> (ctx, cst) + | Exit.Nonzero _ -> (ctx, [ cst ]) | Exit.Zero ctx -> let fields = cst in - let fields = List.map (field_splitting ctx) fields in + let fields = field_splitting ctx fields in let (ctx, cst) : ctx * Ast.fragments list = begin - let glob = Ast.Fragment.join_list ~sep:"" (List.concat fields) in + let glob = Ast.Fragment.join_list ~sep:"" fields in let vs : Ast.fragments list = let has_glob = - List.exists - (fun (f : Ast.fragment) -> f.globbable) - (List.concat fields) + List.exists (fun (f : Ast.fragment) -> f.globbable) fields in let _new_ctx, s = if (not ctx.options.no_path_expansion) && has_glob then glob_expand ctx glob else if ctx.options.no_path_expansion && has_glob then (ctx, [ Ast.Fragment.make glob ]) - else (ctx, List.concat fields) + else (ctx, fields) in [ s ] in @@ -1281,28 +1266,50 @@ module Make (S : Types.State) (E : Types.Exec) = struct let f, p = pipeline p in fold (next_sep, f (handle_pipeline ~async ctx p)) rest) in - fold (Noand_or, Exit.zero ctx) c + match fold (Noand_or, Exit.zero ctx) c with + | Exit.Zero _ as v -> v + | Exit.Nonzero n -> + if Nlist.length c > 1 then + Exit.Nonzero + { + n with + should_exit = { n.should_exit with non_interactive = `No }; + } + else Exit.Nonzero n - and handle_for_clause ctx v : ctx Exit.t = + and handle_for_clause (ctx : ctx) v : ctx Exit.t = match v with | Ast.For_Name_DoGroup (_, (term, sep)) -> exec ctx (term, Some sep) | Ast.For_Name_In_WordList_DoGroup (Name name, wdlist, (term, sep)) -> ( Debug.Log.debug (fun f -> f "for-loop: start"); - let wdlist = Nlist.map (word_expansion ctx) wdlist in - try + (* TODO: fold ctx with exit value... *) + let ctx, wdlist = Nlist.fold_left - (fun _ (_, words) -> - Fiber.yield (); - List.fold_left - (fun _ word -> - Debug.Log.debug (fun f -> - f "for-loop: %s=%s" name word.Ast.txt); - update ctx ~param:name word.Ast.txt >>= fun ctx -> - try exec ctx (term, Some sep) with - | Continue (1, ctx) -> Exit.zero ctx - | Continue (n, ctx) -> raise (Continue (n - 1, ctx))) - (Exit.zero ctx) (List.concat words)) - (Exit.zero ctx) wdlist + (fun (ctx, acc) w -> + let ctx, w = word_expansion (Exit.value ctx) w in + (ctx, acc @ [ w ])) + (Exit.zero ctx, []) + wdlist + in + try + let v : ctx Exit.t = + List.fold_left + (fun (ctx : ctx Exit.t) words -> + Fiber.yield (); + let ctx = Exit.value ctx in + List.fold_left + (fun ctx word -> + Debug.Log.debug (fun f -> + f "for-loop: %s=%s" name word.Ast.txt); + update (Exit.value ctx) ~param:name word.Ast.txt + >>= fun ctx -> + try exec ctx (term, Some sep) with + | Continue (1, ctx) -> Exit.zero ctx + | Continue (n, ctx) -> raise (Continue (n - 1, ctx))) + (Exit.zero ctx) (List.concat words)) + ctx wdlist + in + v with | Break (1, ctx) -> Exit.zero ctx | Break (n, ctx) -> raise (Break (n - 1, ctx))) @@ -1444,7 +1451,10 @@ module Make (S : Types.State) (E : Types.Exec) = struct let ctx = { ctx with argv = Array.of_list argv; subshell = true } in let v = try Option.some @@ handle_compound_command ctx commands - with Return ctx -> Some ctx + with Return ctx -> + Debug.Log.info (fun f -> + f "function %s returned %a" name Exit.pp ctx); + Some ctx in Debug.Log.debug (fun f -> f "function leave: %s" name); v @@ -1473,6 +1483,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct run_subshells cc and glob_expand ctx pattern : ctx * Ast.fragment list = + Debug.Log.debug (fun f -> f "glob %S" pattern); ( ctx, match Glob.glob_dir pattern with | [] -> @@ -1481,7 +1492,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct | exception e -> Debug.Log.debug (fun f -> f "Glob expand exception: %s" (Printexc.to_string e)); - [ Ast.Fragment.make pattern ] + [] | xs -> Debug.Log.debug (fun f -> f "Globbed %s to [%a]" pattern Fmt.(list (quote string)) xs); @@ -1540,11 +1551,22 @@ module Make (S : Types.State) (E : Types.Exec) = struct let ctx, cst = word_expansion ctx wc in match ctx with | Exit.Nonzero _ as ctx -> (ctx, acc) - | Exit.Zero _ as ctx -> (ctx, acc @ cst)))) + | Exit.Zero _ as ctx -> + (ctx, acc @ List.map Ast.Fragment.handle_joins cst)))) (Exit.zero ctx, []) swc in - (ctx, List.map Ast.Fragment.to_string @@ List.concat fs) + let arguments = List.map Ast.Fragment.to_string (List.concat fs) in + (* TODO: Proper handling of escaped stuff? *) + let arguments = + List.map + (fun s -> + match String.get s 0 with + | '\\' -> String.sub s 1 (String.length s - 1) + | (exception _) | _ -> s) + arguments + in + (ctx, arguments) and handle_built_in ~rdrs ~(stdout : Eio_unix.sink_ty Eio.Flow.sink) (ctx : ctx) v = @@ -1708,7 +1730,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct | Shift n -> let n = Option.value ~default:1 n in let new_len = Array.length ctx.argv - n in - if new_len < 0 then begin + if new_len <= 0 then begin Fmt.epr "Shift count out of range\n%!"; Exit.nonzero ctx 1 end @@ -1762,7 +1784,7 @@ module Make (S : Types.State) (E : Types.Exec) = struct (* Handled separately *) assert false - and exec initial_ctx ((command, sep) : Ast.complete_command) = + and exec (initial_ctx : ctx) ((command, sep) : Ast.complete_command) = let rec loop : ?async:bool -> Eio.Switch.t -> ctx -> Ast.clist -> ctx Exit.t = fun ?(async = false) sw ctx -> function @@ -1824,12 +1846,14 @@ module Make (S : Types.State) (E : Types.Exec) = struct should_exit.non_interactive, ctx.subshell, ctx.interactive, + ctx.options.errexit, commands ) with - | `Yes, _, false, true, [] | _, `Yes, false, false, [] -> + | `Yes, _, false, true, _, [] | _, `Yes, false, false, _, [] -> if should_exit.interactive = `Yes && ctx.interactive then Fmt.epr "exit\n%!"; Stdlib.exit exit_code + | _, `Yes, _, false, true, _ -> Stdlib.exit exit_code | _ -> loop_commands (exit, c :: cs) commands) | Exit.Zero _ as ctx -> loop_commands (ctx, c :: cs) commands) in diff --git a/src/lib/wordexp.ml b/src/lib/wordexp.ml index 5d1e4e1..054626c 100644 --- a/src/lib/wordexp.ml +++ b/src/lib/wordexp.ml @@ -1 +1,13 @@ -(* Word expansion. *) +(*----------------------------------------------------------------- + Copyright (c) 2025 The merry programmers. All rights reserved. + SPDX-License-Identifier: ISC + -----------------------------------------------------------------*) + +(* This file handles word expansion, a particularly tricky part of the + POSIX specification. *) + +module Make (S : Types.State) = struct + let tilde_expansion state = function + | Ast.WordTildePrefix _ -> Ast.WordTildePrefix (S.expand state `Tilde) + | v -> v +end diff --git a/test/docker/Dockerfile.debootstrap b/test/docker/Dockerfile.debootstrap index 6670d43..7da21bc 100644 --- a/test/docker/Dockerfile.debootstrap +++ b/test/docker/Dockerfile.debootstrap @@ -11,6 +11,5 @@ RUN opam exec -- dune build --profile=release FROM debian:13 COPY --from=builder /home/opam/src/_build/default/src/bin/main.exe /bin/msh RUN ln -sf /bin/msh /bin/sh -RUN apt-get update - # && apt-get install -y vim +RUN apt-get update && apt-get install -y vim debootstrap ENTRYPOINT [ "msh" ] diff --git a/test/dune b/test/dune index f82971c..f40f174 100644 --- a/test/dune +++ b/test/dune @@ -1,10 +1,7 @@ -(cram - (package merry) - (deps %{bin:msh})) - -(test - (name test_merry) - (libraries eio morbig)) +; (cram +; (package merry) +; (deps %{bin:msh})) +; (test (name wordexp) diff --git a/test/forloops.t b/test/forloops.t index 61b52a4..d4822f9 100644 --- a/test/forloops.t +++ b/test/forloops.t @@ -80,3 +80,21 @@ For loops a b +1.7 Good handling of the for-loop context. + + $ cat > test.sh << EOF + > VALUES="a b c d" + > ok="false" + > + > for v in \$VALUES; do + > if [ \$v = "c" ]; then ok="true"; fi + > done + > + > echo "ok is \$ok" + > EOF + + $ sh test.sh + ok is true + $ msh test.sh + ok is true + diff --git a/test/simple.t b/test/simple.t index f1255c2..e7a3a42 100644 --- a/test/simple.t +++ b/test/simple.t @@ -98,6 +98,19 @@ Some variable expansions: [world] [from the shell] + $ cat > test.sh << EOF + > BASEDIR=/home/bactrian + > f () { + > echo "\$1" + > } + > f \$BASEDIR/documents + > EOF + + $ sh test.sh + /home/bactrian/documents + $ msh test.sh + /home/bactrian/documents + 2. Pipelines with And|Or 2.1 Simple Or @@ -128,7 +141,6 @@ for example the command not being found here :/ > EOF $ msh test.sh - [1] 2.3 Simple And and Or @@ -140,6 +152,17 @@ for example the command not being found here :/ first second + $ cat > test.sh < set -e + > echo "hello" + > false && echo "nope" + > echo "done" + > EOF + + $ msh test.sh + hello + done + 2.4 Simple Pipeline $ msh -c "echo hello | rev" diff --git a/test/wordexp.ml b/test/wordexp.ml index 22c075f..bf2e9f5 100644 --- a/test/wordexp.ml +++ b/test/wordexp.ml @@ -2,7 +2,10 @@ open Merry module C = Merry.Eval.Make (Merry_posix.State) (Merry_posix.Exec) -let expand ctx cst = C.word_expansion ctx cst |> snd |> List.concat +let expand ctx cst = + let v = List.concat_map (fun v -> snd (C.word_expansion ctx v)) cst in + List.concat v + let fragment = Alcotest.of_pp Merry.Ast.Fragment.pp let fragments = Alcotest.list fragment let frags = List.map Ast.Fragment.make @@ -45,7 +48,7 @@ end let test_no_expansions env () = let args = [ "echo"; "hello" ] in - let cargs = W.[ name "echo"; lit "hello" ] in + let cargs = W.[ [ name "echo" ]; [ lit "hello" ] ] in with_default_ctx ~args env @@ fun ctx -> let expected = frags args in let actual = expand ctx cargs in @@ -53,7 +56,7 @@ let test_no_expansions env () = let test_dquote env () = let args = [ "echo"; "\"hello there\"" ] in - let cargs = W.[ name "echo"; dquote [ lit "hello there" ] ] in + let cargs = W.[ [ name "echo" ]; [ dquote [ lit "hello there" ] ] ] in with_default_ctx ~args env @@ fun ctx -> let expected = Ast.[ Fragment.make "echo"; Fragment.make ~join:`No "hello there" ] @@ -64,7 +67,7 @@ let test_dquote env () = let test_dquote_expansion env () = let args = [ "echo"; "\"hello $FOO...\"" ] in let cargs = - W.[ name "echo"; dquote [ lit "hello "; var "FOO"; lit "..." ] ] + W.[ [ name "echo" ]; [ dquote [ lit "hello "; var "FOO"; lit "..." ] ] ] in with_default_ctx ~args ~params:[ ("FOO", "there") ] env @@ fun ctx -> let expected = Ast.Fragment.[ make "echo"; make "hello there..." ] in @@ -73,21 +76,23 @@ let test_dquote_expansion env () = let test_single_expansion env () = let args = [ "echo"; "$FOO" ] in - let cargs = W.[ name "echo"; var "FOO" ] in + let cargs = W.[ [ name "echo" ]; [ var "FOO" ] ] in with_default_ctx ~args ~params:[ ("FOO", "bar") ] env @@ fun ctx -> let expected = [ Ast.Fragment.make "echo"; Ast.Fragment.make "bar" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_argv_expansion env () = - let cargs = W.[ name "echo"; var "@" ] in + let cargs = W.[ [ name "echo" ]; [ var "@" ] ] in with_default_ctx ~args:[ "echo"; "a"; "b"; "c d" ] env @@ fun ctx -> let expected = frags [ "echo"; "a"; "b"; "c"; "d" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_argv_in_quotes_expansion env () = - let cargs = W.[ name "echo"; dquote [ lit "got ["; var "@"; lit "]" ] ] in + let cargs = + W.[ [ name "echo" ]; [ dquote [ lit "got ["; var "@"; lit "]" ] ] ] + in with_default_ctx ~args:[ "echo"; "a"; "b"; "c" ] env @@ fun ctx -> let expected = Ast.Fragment.[ make "echo"; make "got [a"; make "b"; make "c]" ] @@ -99,12 +104,14 @@ let test_arg_in_quotes_expansion env () = let cargs = W. [ - dquote - [ - lit "use `"; - var_with_prefix ~prefix:W.[ lit "*/" ] "0"; - lit " --help'"; - ]; + [ + dquote + [ + lit "use `"; + var_with_prefix ~prefix:W.[ lit "*/" ] "0"; + lit " --help'"; + ]; + ]; ] in with_default_ctx ~args:[ "/some/file/test.sh" ] env @@ fun ctx -> @@ -113,14 +120,14 @@ let test_arg_in_quotes_expansion env () = Alcotest.check fragments "same fragments" expected actual let test_glob env () = - let cargs = W.[ glob_all; lit ".ml" ] in + let cargs = W.[ [ glob_all; lit ".ml" ] ] in with_default_ctx ~args:[ "*.ml" ] env @@ fun ctx -> let expected = Ast.Fragment.[ make "test_merry.ml"; make "wordexp.ml" ] in let actual = expand ctx cargs in Alcotest.check fragments "same fragments" expected actual let test_tilde env () = - let cargs = W.[ tilde "~"; lit "documents" ] in + let cargs = W.[ [ tilde "~"; lit "documents" ] ] in with_default_ctx ~args:[ "~/documents" ] env @@ fun ctx -> let expected = Ast.Fragment.[ make "/home/merry/documents" ] in let actual = expand ctx cargs in