Shells in OCaml

Set -e

This adds the errexit option for the set built-in, although it does not
do an awful lot with it.

Additionally, this also allows Morbig to recognise empty assignments
like `FOO=`.

+47 -7
+21 -5
src/lib/built_ins.ml
··· 2 2 type t = { 3 3 noclobber : bool; 4 4 pipefail : bool; 5 + errexit : bool; 5 6 no_path_expansion : bool; 6 7 no_unset : bool; 7 8 async : bool; ··· 11 12 { 12 13 noclobber = false; 13 14 pipefail = false; 15 + errexit = false; 14 16 no_path_expansion = false; 15 17 no_unset = false; 16 18 async = false; 17 19 } 18 20 19 - let with_options ?noclobber ?pipefail ?async ?no_path_expansion ?no_unset t = 21 + let with_options ?noclobber ?pipefail ?errexit ?async ?no_path_expansion 22 + ?no_unset t = 20 23 { 21 24 noclobber = Option.value ~default:t.noclobber noclobber; 22 25 pipefail = Option.value ~default:t.pipefail pipefail; 26 + errexit = Option.value ~default:t.errexit errexit; 23 27 async = Option.value ~default:t.async async; 24 28 no_path_expansion = 25 29 Option.value ~default:t.no_path_expansion no_path_expansion; 26 30 no_unset = Option.value ~default:t.no_unset no_unset; 27 31 } 28 32 29 - type posix = [ `Noclobber | `Pipefail | `Noglob | `Nounset ] 33 + type posix = [ `Noclobber | `Pipefail | `Noglob | `Nounset | `Errexit ] 30 34 type merry = [ `Async ] 31 35 type option = [ posix | merry ] 32 36 ··· 42 46 | `Pipefail -> with_options ~pipefail:true d 43 47 | `Noclobber -> with_options ~noclobber:true d 44 48 | `Noglob -> with_options ~no_path_expansion:true d 49 + | `Errexit -> with_options ~errexit:true d 45 50 | `Nounset -> with_options ~no_unset:true d 46 51 | `Async -> with_options ~async:true d) 47 52 t options ··· 51 56 Fmt.pf ppf "%-12s %s@." name (if value then "on" else "off") 52 57 in 53 58 let opts = 54 - let { noclobber; pipefail; async; no_path_expansion; no_unset } = opt in 59 + let { noclobber; pipefail; async; no_path_expansion; no_unset; errexit } = 60 + opt 61 + in 55 62 [ 56 63 ("pipefail", pipefail); 64 + ("errexit", errexit); 57 65 ("noclobber", noclobber); 58 66 ("noglob", no_path_expansion); 59 67 ("nounset", no_unset); ··· 141 149 [ 142 150 ("pipefail", `Pipefail); 143 151 ("noclobber", `Noclobber); 152 + ("errexit", `Errexit); 144 153 ("noglob", `Noglob); 145 154 ("nounset", `Nounset); 146 155 ("async", `Async); ··· 149 158 let option = 150 159 let doc = "Options." in 151 160 Arg.(value & opt_all (enum enum_map) [] & info [ "o" ] ~docv:"OPTION" ~doc) 161 + 162 + let errexit = 163 + let doc = "Exit on error, like -o errexit." in 164 + Arg.(value & flag & info [ "e" ] ~docv:"ERREXIT" ~doc) 152 165 153 166 let noclobber = 154 167 let doc = "No clobber, like -o noclobber." in ··· 163 176 Arg.(value & flag & info [ "u" ] ~docv:"NOUNSET" ~doc) 164 177 165 178 let t = 166 - let make_set update noglob noclobber nounset = 179 + let make_set update noglob noclobber nounset errexit = 167 180 let extra = if noglob then [ `Noglob ] else [] in 168 181 let extra = if noclobber then `Noclobber :: extra else extra in 169 182 let extra = if nounset then `Nounset :: extra else extra in 183 + let extra = if errexit then `Errexit :: extra else extra in 170 184 let update = extra @ update in 171 185 Set { update; print_options = false } 172 186 in 173 - let term = Term.(const make_set $ option $ noglob $ noclobber $ nounset) in 187 + let term = 188 + Term.(const make_set $ option $ noglob $ noclobber $ nounset $ errexit) 189 + in 174 190 let info = 175 191 let doc = "Set or unset options and positional parameters." in 176 192 Cmd.info "set" ~doc
+3 -1
src/lib/built_ins.mli
··· 2 2 type t = { 3 3 noclobber : bool; 4 4 pipefail : bool; 5 + errexit : bool; 5 6 no_path_expansion : bool; 6 7 no_unset : bool; 7 8 async : bool; ··· 9 10 10 11 val to_letters : t -> string 11 12 12 - type posix = [ `Noclobber | `Pipefail | `Noglob | `Nounset ] 13 + type posix = [ `Noclobber | `Pipefail | `Noglob | `Nounset | `Errexit ] 13 14 type merry = [ `Async ] 14 15 type option = [ posix | merry ] 15 16 ··· 18 19 val with_options : 19 20 ?noclobber:bool -> 20 21 ?pipefail:bool -> 22 + ?errexit:bool -> 21 23 ?async:bool -> 22 24 ?no_path_expansion:bool -> 23 25 ?no_unset:bool ->
+11
test/simple.t
··· 225 225 msh: cannot overwrite existing file 226 226 world 227 227 228 + Also errexit examples from the specification. 229 + 230 + $ sh -c "set -e; (false; echo one) | cat; echo two" 231 + two 232 + $ msh -c "set -e; (false; echo one) | cat; echo two" 233 + two 234 + $ sh -c "set -e; echo $(false; echo one) two" 235 + one two 236 + $ msh -c "set -e; echo $(false; echo one) two" 237 + one two 238 + 228 239 2.7 Sequences 229 240 230 241 A simple, semicolon sequence.
+12 -1
vendor/morbig.0.11.0/src/prelexerState.ml
··· 26 26 27 27 and quote_kind = SingleQuote | DoubleQuote | OpeningBrace 28 28 29 + let pp_atom ppf = function 30 + | AssignmentMark -> Format.fprintf ppf "|=|" 31 + | ArithmeticMark -> Format.fprintf ppf "|+|" 32 + | QuotingMark _ -> Format.fprintf ppf "\"" 33 + | WordComponent (c, _) -> Format.fprintf ppf "%s" c 34 + 29 35 module AtomBuffer : sig 30 36 type t 31 37 val get : t -> atom list ··· 317 323 318 324 let recognize_assignment current = 319 325 let rhs, prefix = take_until is_assignment_mark (buffer current) in 320 - if prefix = buffer current then ( 326 + let is_empty_assignment = 327 + match rhs, prefix with 328 + | [], AssignmentMark :: _ -> true 329 + | _ -> false 330 + in 331 + if prefix = buffer current && not is_empty_assignment then ( 321 332 current 322 333 ) else 323 334 let buffer = AtomBuffer.make (rhs @ List.tl prefix) in