src/ez/ends.bend
ezx@1.4.0.0 · 70 lines · 0 definitions
1# ez/ends: how the commands that are not planners end. `ez check` and
2# `ez build` start one bend and end on its answer; `ez test` and `ez prove`
3# start one per file and end on the count. Each ends with a `P.Outcome`, the
4# same end a planner's plan has, and the interpreter exits with its
5# `P.status` (`Run.end`), so the status each exits with is a law (EZ-OUT-1).
6# That the answer is what bend printed is EZ-TRUST-2. `ez run` reads no
7# answer: it exits with the program's own status (`code` in ez/start.bend).
8import Base
9# snap's answer reading, from the file its main.bend re-exports it from:
10# main.bend itself reaches the foreign effects that run programs, which a
11# law may not import (bend 2.0.32). `R.ok` and `R.text` are the same defs
12# (snap's SNAP-TRUST-5, trusted as EZ-TRUST-9).
13import snap@1.1.0.0/src/answer.bend as R
14import ezx@1.4.0.0/src/lock/plan.bend as P
15
16# a command that ends on whether it passed: success, or a refusal whose
17# reason was already printed
18def outcome.of(passed: Bool) -> P.Outcome:
19 Bool.pick(P.Outcome, passed, P.Success{}, P.Refused{""})
20
21# `ez lock --package NAME` without `--upgrade` is a line ez refuses before it
22# reads anything, since `--package` only filters an upgrade
23def lock.flags(up: Bool, +name: String) -> P.Outcome:
24 Bool.pick(P.Outcome, Bool.or(up, String.is_empty(name)), P.Success{},
25 P.Refused{"ez: --package needs --upgrade"})
26
27# `ez build`: bend exited 0
28def ran(+out: String) -> P.Outcome:
29 outcome.of(R.ok(out))
30
31# `bend f -o out` checks without running main. A clean file prints nothing at
32# all, and a library entry, having no main, checks clean and then exits 1 with
33# "no main to run" from the emit. A check failure throws before the emit, so
34# that message can only follow a check that passed.
35#
36# It is matched as a whole line, because that is the shape of the thing being
37# claimed: bend prints it as `Error: no main to run` and nothing else. Asked as
38# a substring of the whole output, the same words appearing anywhere (inside
39# some other error's text, or in a source line bend echoed back while
40# reporting on it) would answer yes and mean nothing, and a failing check
41# would read as a passing one. Two other line-shaped facts in this tree were
42# being asked of whole files the same way, and one of them silently dropped a
43# test before it was caught.
44def no.main(+line: String) -> Bool:
45 String.eq(line, "Error: no main to run")
46
47# whether any line of the output is that one. The rest of the walk is bound
48# before the `Bool.or` rather than written under it: `Bool.or` is an ordinary
49# function and reduces both sides, so a recursive call sitting inside one is
50# not a branch the condition can decline. Bound first it is a plain fold over
51# a handful of lines, which is what the rest of this tree does too.
52def said.no.main(ls: List<&2, String>) -> Bool:
53 match ls:
54 case Nil{}:
55 False{}
56 case Con{+h, t}:
57 +rest = said.no.main(t)
58 Bool.or(no.main(h), rest)
59
60# whether `ez check` passed: bend exited 0, or said only that there is no main
61def check.passed(+out: String) -> Bool:
62 Bool.or(R.ok(out), said.no.main(String.lines(R.text(out))))
63
64# `ez check`
65def checked(+out: String) -> P.Outcome:
66 outcome.of(check.passed(out))
67
68# `ez test` and `ez prove`: every file passed
69def counted(+passed: Nat, +total: Nat) -> P.Outcome:
70 outcome.of(Nat.is_eq(passed, total))