1 # src/par: what `par` sends its effect and how it reads the answers back. 2 # `par.plan` frames every job on one wire, sending a job `run` would refuse as 3 # the empty job; `par.answers` pairs each status with the body its job wrote. 4 import Base 5 import ./argv.bend as Argv 6 import ./file.bend as File 7 8 # one job's fields on the `par` wire: each argument marked with a `+` in 9 # front, then an empty field that ends the job. A marked field is never empty, 10 # so the empty one cannot be mistaken for an argument, whatever the arguments 11 # hold; and the empty job is the empty field alone. 12 def job.fields(argv: List<&2, String>) -> List<&2, String>: 13 match argv: 14 case []: 15 [""] 16 case arg <> rest: 17 ("+" ++ arg) <> job.fields(rest) 18 19 # a job accepted as `run` would accept it is sent as it is; any other is sent 20 # as the empty job, which the effect answers as a program that could not be 21 # started, in its own place 22 def par.job.of(ok: Bool, argv: List<&2, String>) -> List<&2, String>: 23 match ok: 24 case True{}: 25 argv 26 case False{}: 27 [] 28 29 # the job `par` sends for an argv 30 def par.job(+argv: List<&2, String>) -> List<&2, String>: 31 par.job.of(Argv.accepts(argv), argv) 32 33 # every job's fields, in the order the jobs were given 34 def par.fields(js: List<&2, List<&2, String>>) -> List<&2, String>: 35 match js: 36 case []: 37 [] 38 case job <> rest: 39 List.append(&2, String, job.fields(par.job(job)), par.fields(rest)) 40 41 # what the `par` effect is handed: how many may run at once, how many gigabytes 42 # each was given, where they write, the second they must all be finished by, 43 # and then every job's fields, all joined on NUL 44 def par.plan(js: List<&2, List<&2, String>>, width: String, gb: String, at: String, by: String) -> String: 45 String.join(width <> gb <> at <> by <> par.fields(js), "\0") 46 47 # the strings that are not empty, which is what a list of statuses amounts to 48 # once a trailing newline has been counted as a line 49 def par.nonblank(ss: List<&2, String>) -> List<&2, String>: 50 match ss: 51 case Nil{}: 52 Nil{} 53 case Con{+h, t}: 54 +more = par.nonblank(t) 55 Bool.pick(List<&2, String>, String.is_empty(h), more, h <> more) 56 57 # one job's answer, in the shape `run` gives: the status on its own first 58 # line, then everything the program printed, read back out of the file it was 59 # pointed at 60 def par.of(+code: String, got: Maybe<&2, String>) -> String: 61 code ++ "\n" ++ File.file.text_of(got) 62 63 # every job's answer: each status paired with the body at its place in the 64 # order, or with none when there is no body there 65 def par.answers(cs: List<&2, String>, bodies: List<&2, Maybe<&2, String>>) -> List<&2, String>: 66 match cs bodies: 67 case Nil{} _bs: 68 [] 69 case Con{+code, ct} Nil{}: 70 par.of(code, None{}) <> par.answers(ct, []) 71 case Con{+code, ct} Con{body, bt}: 72 par.of(code, body) <> par.answers(ct, bt) 73 74 # the file each job wrote, one per status, by its place in the order 75 def par.bodies(cs: List<&2, String>, +at: String, +nn: Nat) -> IO(List<&2, Maybe<&2, String>>): 76 match cs: 77 case []: 78 IO.pure(List<&2, Maybe<&2, String>>, []) 79 case _code <> ct: 80 do IO>>: 81 src : Maybe<&2, String> <- File.file.read(at ++ "/" ++ Nat.show(nn)) 82 more : List<&2, Maybe<&2, String>> <- par.bodies(ct, at, (1n + nn : Nat)) 83 return src <> more