src/par.bend
snap@1.2.0.0 · 83 lines · 0 definitions
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.
4import Base
5import snap@1.2.0.0/src/argv.bend as Argv
6import snap@1.2.0.0/src/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.
12def 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
22def 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
30def 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
34def 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
44def 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
49def 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
60def 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
65def 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
75def 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<List<&2, Maybe<&2, String>>>:
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