# share/args: the command line, as the binary itself receives it. Since bend # 2.0.32 `IO.args()` starts with the program as it was invoked, as C's argv # does, so `all` drops it: the first word left is the subcommand and the rest # are its own. A compiled binary's runtime keeps `--threads N`, `--gpu V`, # `--gpu-build` and `--bend-help` for itself and strips them wherever they # appear, the first two with the value after them; the last two answer and # exit before the program starts. The first `--` is dropped and everything # after it reaches the program untouched, those four included. `--help` is # the program's since 2.0.29, and ez reads it as a request for help # (ez/line.bend). import Base # the non-empty strings def nonempty(ss: List<&2, String>) -> List<&2, String>: match ss: case Nil{}: Nil{} case Con{+h, t}: +more = nonempty(t) Bool.pick(List<&2, String>, String.is_empty(h), more, h <> more) # a variable's text; unset, "" def text_of(res: Result<&1, &1, U32 & String, String>) -> String: match res: case Fail{_e}: "" case Done{s}: s # the arguments the process was started with. `IO.args()` answers a `&1` list # and the rest of ez reads its arguments more than once, so the walk copies it. def copy(xs: List<&1, String>) -> List<&2, String>: match xs: case Nil{}: Nil{} case Con{h, t}: h <> copy(t) # the first argument and the ones after it. The pair is returned rather than # asked about twice, since a match cannot scrutinise a local binder. def split(xs: List<&2, String>) -> String & List<&2, String>: match xs: case Nil{}: ("", Nil{}) case Con{h, t}: (h, t) # the nth argument, or "" when it was not given. Both the list and the count # are scrutinised together, so the walk needs no helper to branch through. def at(xs: List<&2, String>, index: Nat) -> String: match xs index: case Nil{} _m: "" case Con{h, _t} 0n: h case Con{_h, t} 1n+p: at(t, p) # every word after the first, which is what `run` forwards to the entry def rest(as: List<&2, String>) -> List<&2, String>: match as: case Nil{}: Nil{} case Con{_h, t}: t # every argument after the program's own name, each one readable twice def all() -> IO(List<&2, String>): do IO>: xs : List<&1, String> <- IO.args() return rest(copy(xs)) # the command `ez run` starts: bend on the entry, with every word after # `run` (EZ-TOOL-9) def run.line(+entry: String, as: List<&2, String>) -> List<&2, String>: "bend" <> (entry <> rest(as)) # `n` words dropped. What remains when the list runs out first is nothing. def drop(xs: List<&2, String>, count: Nat) -> List<&2, String>: match xs count: case Nil{} _m: Nil{} case Con{+h, +t} 0n: h <> t case Con{_h, t} 1n+p: drop(t, p) # a leading `--` is the separator, not a word the program was given def undash(xs: List<&2, String>) -> List<&2, String>: match xs: case Nil{}: Nil{} case Con{+h, +t}: Bool.pick(List<&2, String>, String.eq(h, "--"), t, h <> t) # the words after `tool run` with a leading `--entry ` taken off. The # flag comes before the target, as uvx takes its own options before the # command, so every word after the target is the program's. def tool.lead(xs: List<&2, String>) -> List<&2, String>: match xs: case Nil{}: Nil{} case Con{+h, +t}: Bool.pick(List<&2, String>, String.eq(h, "--entry"), rest(t), h <> t) # the words `ez tool run` forwards: everything after the target, with one # leading `--` taken off def tool.rest(as: List<&2, String>) -> List<&2, String>: undash(rest(tool.lead(drop(as, 2n)))) # `tool run` with its target, and without the words the program is given. # A missing target is left missing, so the parse can say so. def tool.line(+target: String) -> List<&2, String>: Bool.pick(List<&2, String>, String.is_empty(target), ["tool", "run"], ["tool", "run", target]) # `tool run` with its `--entry ` and target, when it has the flag def tool.flagged(+file: String, +target: String) -> List<&2, String>: Bool.pick(List<&2, String>, String.is_empty(target), ["tool", "run", "--entry", file], ["tool", "run", "--entry", file, target]) # the words of `tool run` the parse reads: the flag when it leads, and the # target def tool.parse(lead: Bool, +as: List<&2, String>) -> List<&2, String>: match lead: case True{}: tool.flagged(at(as, 3n), at(as, 4n)) case False{}: tool.line(at(as, 2n)) # whether this line is `tool run` def is.tool.run(+as: List<&2, String>) -> Bool: Bool.and(String.eq(at(as, 0n), "tool"), String.eq(at(as, 1n), "run")) # `tool run` parsed as the command and its target; any other line as itself def argv.tool(hit: Bool, +as: List<&2, String>) -> List<&2, String>: match hit: case True{}: tool.parse(String.eq(at(as, 2n), "--entry"), as) case False{}: as # `run` parsed as the command alone, so the words it forwards are not flags def argv.run(hit: Bool, +as: List<&2, String>) -> List<&2, String>: match hit: case True{}: ["run"] case False{}: argv.tool(is.tool.run(as), as) # a `run` line and a `tool run` line are parsed without the words they # forward. Every other line is parsed as it arrived. def argv.of(+as: List<&2, String>) -> List<&2, String>: argv.run(String.eq(at(as, 0n), "run"), as)