src/par.bend

snap@1.1.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.1.0.0/src/argv.bend as Argv
6import snap@1.1.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