1 # share/args: the command line, as the binary itself receives it. Since bend 2 # 2.0.32 `IO.args()` starts with the program as it was invoked, as C's argv 3 # does, so `all` drops it: the first word left is the subcommand and the rest 4 # are its own. A compiled binary's runtime keeps `--threads N`, `--gpu V`, 5 # `--gpu-build` and `--bend-help` for itself and strips them wherever they 6 # appear, the first two with the value after them; the last two answer and 7 # exit before the program starts. The first `--` is dropped and everything 8 # after it reaches the program untouched, those four included. `--help` is 9 # the program's since 2.0.29, and ez reads it as a request for help 10 # (ez/line.bend). 11 import Base 12 13 # the non-empty strings 14 def nonempty(ss: List<&2, String>) -> List<&2, String>: 15 match ss: 16 case Nil{}: 17 Nil{} 18 case Con{+h, t}: 19 +more = nonempty(t) 20 Bool.pick(List<&2, String>, String.is_empty(h), more, h <> more) 21 22 # a variable's text; unset, "" 23 def text_of(res: Result<&1, &1, U32 & String, String>) -> String: 24 match res: 25 case Fail{_e}: 26 "" 27 case Done{s}: 28 s 29 30 # the arguments the process was started with. `IO.args()` answers a `&1` list 31 # and the rest of ez reads its arguments more than once, so the walk copies it. 32 def copy(xs: List<&1, String>) -> List<&2, String>: 33 match xs: 34 case Nil{}: 35 Nil{} 36 case Con{h, t}: 37 h <> copy(t) 38 39 # the first argument and the ones after it. The pair is returned rather than 40 # asked about twice, since a match cannot scrutinise a local binder. 41 def split(xs: List<&2, String>) -> String & List<&2, String>: 42 match xs: 43 case Nil{}: 44 ("", Nil{}) 45 case Con{h, t}: 46 (h, t) 47 48 # the nth argument, or "" when it was not given. Both the list and the count 49 # are scrutinised together, so the walk needs no helper to branch through. 50 def at(xs: List<&2, String>, index: Nat) -> String: 51 match xs index: 52 case Nil{} _m: 53 "" 54 case Con{h, _t} 0n: 55 h 56 case Con{_h, t} 1n+p: 57 at(t, p) 58 59 # every word after the first, which is what `run` forwards to the entry 60 def rest(as: List<&2, String>) -> List<&2, String>: 61 match as: 62 case Nil{}: 63 Nil{} 64 case Con{_h, t}: 65 t 66 67 # every argument after the program's own name, each one readable twice 68 def all() -> IO(List<&2, String>): 69 do IO>: 70 xs : List<&1, String> <- IO.args() 71 return rest(copy(xs)) 72 73 # the command `ez run` starts: bend on the entry, with every word after 74 # `run` (EZ-TOOL-9) 75 def run.line(+entry: String, as: List<&2, String>) -> List<&2, String>: 76 "bend" <> (entry <> rest(as)) 77 78 # `n` words dropped. What remains when the list runs out first is nothing. 79 def drop(xs: List<&2, String>, count: Nat) -> List<&2, String>: 80 match xs count: 81 case Nil{} _m: 82 Nil{} 83 case Con{+h, +t} 0n: 84 h <> t 85 case Con{_h, t} 1n+p: 86 drop(t, p) 87 88 # a leading `--` is the separator, not a word the program was given 89 def undash(xs: List<&2, String>) -> List<&2, String>: 90 match xs: 91 case Nil{}: 92 Nil{} 93 case Con{+h, +t}: 94 Bool.pick(List<&2, String>, String.eq(h, "--"), t, h <> t) 95 96 # the words after `tool run` with a leading `--entry ` taken off. The 97 # flag comes before the target, as uvx takes its own options before the 98 # command, so every word after the target is the program's. 99 def tool.lead(xs: List<&2, String>) -> List<&2, String>: 100 match xs: 101 case Nil{}: 102 Nil{} 103 case Con{+h, +t}: 104 Bool.pick(List<&2, String>, String.eq(h, "--entry"), rest(t), h <> t) 105 106 # the words `ez tool run` forwards: everything after the target, with one 107 # leading `--` taken off 108 def tool.rest(as: List<&2, String>) -> List<&2, String>: 109 undash(rest(tool.lead(drop(as, 2n)))) 110 111 # `tool run` with its target, and without the words the program is given. 112 # A missing target is left missing, so the parse can say so. 113 def tool.line(+target: String) -> List<&2, String>: 114 Bool.pick(List<&2, String>, String.is_empty(target), ["tool", "run"], 115 ["tool", "run", target]) 116 117 # `tool run` with its `--entry ` and target, when it has the flag 118 def tool.flagged(+file: String, +target: String) -> List<&2, String>: 119 Bool.pick(List<&2, String>, String.is_empty(target), ["tool", "run", "--entry", file], 120 ["tool", "run", "--entry", file, target]) 121 122 # the words of `tool run` the parse reads: the flag when it leads, and the 123 # target 124 def tool.parse(lead: Bool, +as: List<&2, String>) -> List<&2, String>: 125 match lead: 126 case True{}: 127 tool.flagged(at(as, 3n), at(as, 4n)) 128 case False{}: 129 tool.line(at(as, 2n)) 130 131 # whether this line is `tool run` 132 def is.tool.run(+as: List<&2, String>) -> Bool: 133 Bool.and(String.eq(at(as, 0n), "tool"), String.eq(at(as, 1n), "run")) 134 135 # `tool run` parsed as the command and its target; any other line as itself 136 def argv.tool(hit: Bool, +as: List<&2, String>) -> List<&2, String>: 137 match hit: 138 case True{}: 139 tool.parse(String.eq(at(as, 2n), "--entry"), as) 140 case False{}: 141 as 142 143 # `run` parsed as the command alone, so the words it forwards are not flags 144 def argv.run(hit: Bool, +as: List<&2, String>) -> List<&2, String>: 145 match hit: 146 case True{}: 147 ["run"] 148 case False{}: 149 argv.tool(is.tool.run(as), as) 150 151 # a `run` line and a `tool run` line are parsed without the words they 152 # forward. Every other line is parsed as it arrived. 153 def argv.of(+as: List<&2, String>) -> List<&2, String>: 154 argv.run(String.eq(at(as, 0n), "run"), as)