···3232 | _ -> 0
3333 in
3434 let update state s i =
3535- S.update state ~param:s [ Ast.WordLiteral (string_of_int i) ]
3535+ match S.update state ~param:s [ Ast.WordLiteral (string_of_int i) ] with
3636+ | Ok s -> s
3737+ | Error m -> failwith m
3638 in
3739 let rec calc state = function
3840 | Int i -> (state, i)
+19
src/lib/built_ins.ml
···5050 | Alias
5151 | Unalias
5252 | Eval of string list
5353+ | Echo of string list
53545455(* Change Directory *)
5556module Cd = struct
···250251 Cmd.v info term
251252end
252253254254+module Echo = struct
255255+ open Cmdliner
256256+257257+ let args =
258258+ let doc = "The arguments to echo to standard output." in
259259+ Arg.(value & pos_all string [] & info [] ~docv:"ARGS" ~doc)
260260+261261+ let t =
262262+ let make_echo args = Echo args in
263263+ let term = Term.(const make_echo $ args) in
264264+ let info =
265265+ let doc = "Display a line of text." in
266266+ Cmd.info "echo" ~doc
267267+ in
268268+ Cmd.v info term
269269+end
270270+253271module Source = Make_dot (struct
254272 let name = "source"
255273end)
···282300 | "alias" :: _ -> Some (Ok Alias)
283301 | "unalias" :: _ -> Some (Ok Unalias)
284302 | "eval" :: _ as cmd -> exec_cmd cmd Eval.t
303303+ | "echo" :: _ as cmd -> exec_cmd cmd Echo.t
285304 | _ -> None
+1
src/lib/built_ins.mli
···2727 | Alias
2828 | Unalias
2929 | Eval of string list
3030+ | Echo of string list
30313132val of_args : string list -> (t, string) result option
3233(** Parses a command-line to the built-ins, errors are returned if parsing. *)
+115-51
src/lib/eval.ml
···246246 List.fold_left (fun acc (k, _) -> List.remove_assoc k acc) env extra
247247 |> List.append extra
248248249249+ let update ?export ?readonly ctx ~param v =
250250+ match S.update ?export ?readonly ctx.state ~param v with
251251+ | Ok state -> Exit.zero { ctx with state }
252252+ | Error msg ->
253253+ Fmt.epr "%s\n%!" msg;
254254+ Exit.nonzero ctx 1
255255+249256 let remove_quotes s =
250257 let s_len = String.length s in
251258 if s.[0] = '"' && s.[s_len - 1] = '"' then String.sub s 1 (s_len - 2) else s
···313320 match c with
314321 | Ast.SimpleCommand (Prefixed (prefix, None, _suffix)) :: rest ->
315322 let ctx = collect_assignments ctx prefix in
316316- loop ctx job stdout_of_previous rest
323323+ let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
324324+ loop (Exit.value ctx) job stdout_of_previous rest
317325 | Ast.SimpleCommand (Prefixed (prefix, Some executable, suffix)) :: rest
318326 ->
319327 let ctx = collect_assignments ~update:false ctx prefix in
320320- loop ctx job stdout_of_previous
328328+ let job = handle_job job (`Built_in (ctx >|= fun _ -> ())) in
329329+ loop (Exit.value ctx) job stdout_of_previous
321330 (Ast.SimpleCommand (Named (executable, suffix)) :: rest)
322331 | Ast.SimpleCommand (Named (executable, suffix)) :: rest -> (
323332 let ctx, executable = expand_cst ctx executable in
···377386 raw CST *)
378387 match executable with
379388 | "export" ->
380380- let updated = handle_export ctx args in
389389+ let updated =
390390+ handle_export_or_readonly `Export ctx args
391391+ in
392392+ let job =
393393+ handle_job job (`Built_in (updated >|= fun _ -> ()))
394394+ in
395395+ loop (Exit.value updated) job stdout_of_previous rest
396396+ | "readonly" ->
397397+ let updated =
398398+ handle_export_or_readonly `Readonly ctx args
399399+ in
381400 let job =
382401 handle_job job (`Built_in (updated >|= fun _ -> ()))
383402 in
···530549 prefix
531550 in
532551 let rec expand acc ctx = function
533533- | [] -> (ctx, List.rev acc |> List.concat)
552552+ | [] -> (Exit.zero ctx, List.rev acc |> List.concat)
534553 | Ast.WordVariable v :: rest -> (
535554 match v with
536555 | Ast.VariableAtom ("!", NoAttribute) ->
···619638 | Ast.VariableAtom (s, AssignDefaultValues (_, value)) -> (
620639 match S.lookup ctx.state ~param:s with
621640 | Some cst -> expand (cst :: acc) ctx rest
622622- | None ->
623623- let state = S.update ctx.state ~param:s value in
624624- let new_ctx = { ctx with state } in
625625- expand (value :: acc) new_ctx rest)
641641+ | None -> (
642642+ match S.update ctx.state ~param:s value with
643643+ | Ok state ->
644644+ let new_ctx = { ctx with state } in
645645+ expand (value :: acc) new_ctx rest
646646+ | Error m ->
647647+ ( Exit.nonzero_msg ~exit_code:1 ctx "%s" m,
648648+ List.rev acc |> List.concat )))
626649 | Ast.VariableAtom (_, IndicateErrorifNullorUnset (_, _)) ->
627650 Fmt.failwith "TODO: Indicate Error")
628628- | Ast.WordDoubleQuoted cst :: rest ->
651651+ | Ast.WordDoubleQuoted cst :: rest -> (
629652 let new_ctx, cst_acc = expand [] ctx cst in
630630- expand ([ Ast.WordDoubleQuoted cst_acc ] :: acc) new_ctx rest
631631- | Ast.WordSingleQuoted cst :: rest ->
653653+ match new_ctx with
654654+ | Exit.Nonzero _ -> (new_ctx, cst_acc)
655655+ | Exit.Zero new_ctx ->
656656+ expand ([ Ast.WordDoubleQuoted cst_acc ] :: acc) new_ctx rest)
657657+ | Ast.WordSingleQuoted cst :: rest -> (
632658 let new_ctx, cst_acc = expand [] ctx cst in
633633- expand ([ Ast.WordSingleQuoted cst_acc ] :: acc) new_ctx rest
634634- | Ast.WordAssignmentWord (n, w) :: rest ->
659659+ match new_ctx with
660660+ | Exit.Nonzero _ -> (new_ctx, cst_acc)
661661+ | Exit.Zero new_ctx ->
662662+ expand ([ Ast.WordSingleQuoted cst_acc ] :: acc) new_ctx rest)
663663+ | Ast.WordAssignmentWord (n, w) :: rest -> (
635664 let new_ctx, cst_acc = expand [] ctx w in
636636- expand ([ Ast.WordAssignmentWord (n, cst_acc) ] :: acc) new_ctx rest
665665+ match new_ctx with
666666+ | Exit.Nonzero _ -> (new_ctx, cst_acc)
667667+ | Exit.Zero new_ctx ->
668668+ expand
669669+ ([ Ast.WordAssignmentWord (n, cst_acc) ] :: acc)
670670+ new_ctx rest)
637671 | v :: rest -> expand ([ v ] :: acc) ctx rest
638672 in
639673 expand [] ctx ast
640674641641- and handle_export ctx (assignments : Ast.word_cst list) =
675675+ and handle_export_or_readonly kind ctx (assignments : Ast.word_cst list) =
676676+ let flags, assignments =
677677+ List.fold_left
678678+ (fun (fs, args) -> function
679679+ | [ Ast.WordName v ] | [ Ast.WordLiteral v ] -> (
680680+ match Astring.String.cut ~sep:"-" v with
681681+ | Some ("", f) -> (f :: fs, args)
682682+ | _ -> (fs, [ Ast.WordName v ] :: args))
683683+ | v -> (fs, v :: args))
684684+ ([], []) assignments
685685+ in
686686+ let update =
687687+ match kind with
688688+ | `Export -> update ~export:true ~readonly:false
689689+ | `Readonly -> update ~export:false ~readonly:true
690690+ in
642691 let rec loop acc_ctx = function
643692 | [] -> Exit.zero acc_ctx
644693 | Ast.WordAssignmentWord (Name param, v) :: rest ->
645645- loop
646646- {
647647- acc_ctx with
648648- state = S.update ~export:true acc_ctx.state ~param v;
649649- }
650650- rest
694694+ update acc_ctx ~param v >>= fun new_ctx -> loop new_ctx rest
651695 | Ast.WordName param :: rest -> (
652696 match S.lookup acc_ctx.state ~param with
653697 | Some v ->
654654- loop
655655- {
656656- acc_ctx with
657657- state = S.update ~export:true acc_ctx.state ~param v;
658658- }
659659- rest
698698+ update acc_ctx ~param v >>= fun new_ctx -> loop new_ctx rest
660699 | None -> loop acc_ctx rest)
661700 | c :: _ ->
662701 Exit.nonzero_msg acc_ctx "export weird arguments: %s\n"
663702 (Ast.word_component_to_string c)
664703 in
665665- List.fold_left
666666- (fun ctx w -> match ctx with Exit.Zero ctx -> loop ctx w | _ -> ctx)
667667- (Exit.zero ctx) assignments
704704+ match flags with
705705+ | [] ->
706706+ List.fold_left
707707+ (fun ctx w -> match ctx with Exit.Zero ctx -> loop ctx w | _ -> ctx)
708708+ (Exit.zero ctx) assignments
709709+ | fs ->
710710+ if List.mem "p" fs then begin
711711+ match kind with
712712+ | `Readonly -> S.pp_readonly Fmt.stdout ctx.state
713713+ | `Export -> S.pp_export Fmt.stdout ctx.state
714714+ end;
715715+ Exit.zero ctx
668716669717 and expand_cst (ctx : ctx) cst : ctx * Ast.word_cst =
670718 let cst = tilde_expansion ctx cst in
671719 let ctx, cst = parameter_expansion' ctx cst in
672672- arithmetic_expansion ctx cst
720720+ match ctx with
721721+ | Exit.Nonzero { value = ctx; _ } -> (ctx, cst)
722722+ | Exit.Zero ctx ->
723723+ (* TODO: Propagate errors *)
724724+ let ctx, ast = arithmetic_expansion ctx cst in
725725+ (ctx, ast)
673726674727 and expand_redirects ((ctx, acc) : ctx * Ast.cmd_suffix_item list)
675728 (c : Ast.cmd_suffix_item list) =
···735788 let wdlist = Nlist.flatten @@ Nlist.map (word_glob_expand ctx) wdlist in
736789 Nlist.fold_left
737790 (fun _ word ->
738738- let s = S.update ctx.state ~param:name word in
739739- let ctx = { ctx with state = s } in
740740- exec ctx (term, Some sep))
791791+ update ctx ~param:name word >>= fun ctx -> exec ctx (term, Some sep))
741792 (Exit.zero ctx) wdlist
742793743794 and handle_if_clause ctx = function
···900951 if List.exists needs_glob_expansion wc then glob_expand ctx wc
901952 else [ handle_word_cst_subshell ctx wc ]
902953903903- and collect_assignments ?(update = true) ctx =
954954+ and collect_assignments ?(update = true) ctx vs : ctx Exit.t =
904955 List.fold_left
905905- (fun ctx -> function
906906- | Ast.Prefix_assignment (Name param, v) ->
907907- (* Expand the values *)
908908- let ctx, v = expand_cst ctx v in
909909- let v = handle_subshell ctx v in
910910- let state =
911911- if update then S.update ctx.state ~param v else ctx.state
912912- in
913913- {
914914- ctx with
915915- state;
916916- local_state =
917917- (param, Ast.word_components_to_string v) :: ctx.local_state;
918918- }
919919- | _ -> ctx)
920920- ctx
956956+ (fun ctx prefix ->
957957+ match ctx with
958958+ | Exit.Nonzero _ -> ctx
959959+ | Exit.Zero ctx -> (
960960+ match prefix with
961961+ | Ast.Prefix_assignment (Name param, v) -> (
962962+ (* Expand the values *)
963963+ let ctx, v = expand_cst ctx v in
964964+ let v = handle_subshell ctx v in
965965+ let state =
966966+ if update then S.update ctx.state ~param v else Ok ctx.state
967967+ in
968968+ match state with
969969+ | Error message -> Exit.nonzero ~message ctx 1
970970+ | Ok state ->
971971+ Exit.zero
972972+ {
973973+ ctx with
974974+ state;
975975+ local_state =
976976+ (param, Ast.word_components_to_string v)
977977+ :: ctx.local_state;
978978+ })
979979+ | _ -> Exit.zero ctx))
980980+ (Exit.zero ctx) vs
921981922982 and args ctx swc : ctx * Ast.word_cst list =
923983 List.fold_left
···10101070 let ast = Ast.of_string script in
10111071 let ctx, _ = run (Exit.zero ctx) ast in
10121072 ctx
10731073+ | Echo args ->
10741074+ let str = String.concat " " args ^ "\n" in
10751075+ Eio.Flow.copy_string str stdout;
10761076+ Exit.zero ctx
10131077 | Command _ ->
10141078 (* Handled separately *)
10151079 assert false
+3
src/lib/exit.ml
···4646 | Zero v -> Zero (f v)
4747 | Nonzero ({ value; _ } as v) -> Nonzero { v with value = f value }
48484949+let map_zero ~f = function Zero v -> f v | Nonzero x -> Nonzero x
5050+4951let map' ~zero ~nonzero = function
5052 | Zero v -> Zero (zero v)
5153 | Nonzero v -> Nonzero { v with value = nonzero v.value }
···59616062module Syntax = struct
6163 let ( >|= ) x f = map ~f x
6464+ let ( >>= ) x f = map_zero ~f x
6265 let ( let+ ) = ( >|= )
6366end
+46-6
src/lib/posix/state.ml
···11module Variables = Map.Make (String)
2233+type attributes = { export : bool; readonly : bool }
44+55+let default_attribute = { export = false; readonly = false }
66+37type t = {
48 cwd : Fpath.t;
59 functions : Merry.Function.t list;
610 root : int;
711 outermost : bool;
812 home : string;
99- variables : (bool * Merry.Ast.word_cst) Variables.t;
1313+ variables : (attributes * Merry.Ast.word_cst) Variables.t;
1014}
11151212-let update ?(export = false) t ~param v =
1313- let variables' = Variables.add param (export, v) t.variables in
1414- { t with variables = variables' }
1616+let update ?(export = false) ?(readonly = false) t ~param v =
1717+ match Variables.find_opt param t.variables with
1818+ | Some ({ readonly = true; _ }, _) ->
1919+ Error (Fmt.str "%s: readonly variable" param)
2020+ | _ ->
2121+ let attr = { export; readonly } in
2222+ let variables' = Variables.add param (attr, v) t.variables in
2323+ Ok { t with variables = variables' }
15241625let seed_env () =
1726 let env = Merry.Eunix.env () in
1827 List.fold_left
1928 (fun vars (param, v) ->
2020- Variables.add param (true, [ Merry.Ast.WordName v ]) vars)
2929+ Variables.add param
3030+ ({ default_attribute with export = true }, [ Merry.Ast.WordName v ])
3131+ vars)
2132 Variables.empty env
22332334let make ?(functions = []) ?(root = 0) ?(outermost = true) ?(home = "/root")
···37483849let exports t =
3950 Variables.to_list t.variables
4040- |> List.filter_map (function p, (true, v) -> Some (p, v) | _ -> None)
5151+ |> List.filter_map (function
5252+ | p, ({ export = true; _ }, v) -> Some (p, v)
5353+ | _ -> None)
5454+5555+let readonly t =
5656+ Variables.to_list t.variables
5757+ |> List.filter_map (function
5858+ | p, ({ readonly = true; _ }, v) -> Some (p, v)
5959+ | _ -> None)
6060+6161+let pp_readonly fmt t =
6262+ let rs = readonly t in
6363+ let rs =
6464+ List.map
6565+ (fun (p, cst) ->
6666+ ("readonly " ^ p, Merry.Ast.word_components_to_string cst))
6767+ rs
6868+ in
6969+ Fmt.(list ~sep:(Fmt.any "\n") (pair ~sep:(Fmt.any "=") string (quote string)))
7070+ fmt rs
7171+7272+let pp_export fmt t =
7373+ let rs = exports t in
7474+ let rs =
7575+ List.map
7676+ (fun (p, cst) -> ("export " ^ p, Merry.Ast.word_components_to_string cst))
7777+ rs
7878+ in
7979+ Fmt.(list ~sep:(Fmt.any "\n") (pair ~sep:(Fmt.any "=") string (quote string)))
8080+ fmt rs
41814282let dump ppf s =
4383 Fmt.pf ppf "Variables:[%a]"
+12-1
src/lib/types.ml
···2323 val lookup : t -> param:string -> Ast.word_cst option
2424 (** Parameter lookup. [None] means [unset]. *)
25252626- val update : ?export:bool -> t -> param:string -> Ast.word_cst -> t
2626+ val update :
2727+ ?export:bool ->
2828+ ?readonly:bool ->
2929+ t ->
3030+ param:string ->
3131+ Ast.word_cst ->
3232+ (t, string) result
2733 (** Update the state with a new parameter mapping and whether or not it should
2834 exported to the environment (default false). *)
2935···3440 val exports : t -> (string * Ast.word_cst) list
3541 (** All of the variables that must be exported to the environment *)
36424343+ val readonly : t -> (string * Ast.word_cst) list
4444+ (** All of the variables that must be exported to the environment *)
4545+4646+ val pp_readonly : t Fmt.t
4747+ val pp_export : t Fmt.t
3748 val dump : t Fmt.t
3849end
3950