src/lint.bend

bolt@1.18.0.0 · 114 lines · 0 definitions

1# src/lint: the interpreter for `bolt` itself. It starts from a World
2# holding the paths it was given and the working directory
3# (src/lint/world.bend), then loops: it asks
4# the planner (src/lint/plan.bend) what is still open, answers each question
5# by IO, a directory listed through src/walk/ and a file read through
6# src/lsp/files/ (a path src/walk/ calls a directory is answered as one,
7# not read), and adds the answers to the World, until nothing is left to
8# ask. Then it prints the planner's lines and exits with its status. It
9# decides nothing: which files are linted, which bolt.bend grades each
10# finding, and what is printed are the planner's. That it answers and prints
11# faithfully is BOLT-TRUST-9.
12import Base
13import bolt@1.18.0.0/src/lint/world.bend as W
14import bolt@1.18.0.0/src/lint/plan.bend as Plan
15import bolt@1.18.0.0/src/lsp/files/disk.bend as Disk
16import bolt@1.18.0.0/src/walk/disk.bend as WalkDisk
17import bolt@1.18.0.0/src/status.bend as Status
18import bolt@1.18.0.0/src/lazy/lazy.bend as Lazy
19
20# a file's text as an answer
21def read.answer(mm: Maybe<&2, String>) -> W.Answer:
22  match mm:
23    case None{}:
24      W.Unread{}
25    case Some{text}:
26      W.Read{text}
27
28# a path's text as an answer: a directory has none, and a file is read
29def text.answer(dir: Bool, path: String) -> IO(W.Answer):
30  match dir:
31    case True{}:
32      IO.pure(W.Answer, W.Directory{})
33    case False{}:
34      do IO<W.Answer>:
35        mm : Maybe<&2, String> <- Disk.read(path)
36        return read.answer(mm)
37
38# one question, answered
39def answer(ask: W.Ask) -> IO(W.Answer):  # noqa: L001 IO driver
40  match ask:
41    case W.Entries{dir}:
42      do IO<W.Answer>:
43        es : List<&2, String> <- WalkDisk.entries(dir)
44        return W.Listed{es}
45    case W.Contents{+path}:
46      do IO<W.Answer>:
47        dir : Bool <- WalkDisk.is_dir(path)
48        text.answer(dir, path)
49
50# every question, answered, in order
51def answer.all(asks: List<&2, W.Ask>) -> IO(List<&2, W.Reply>):
52  match asks:
53    case Nil{}:
54      IO.pure(List<&2, W.Reply>, [])
55    case Con{+ask, rest}:
56      do IO<List<&2, W.Reply>>:
57        a : W.Answer <- answer(ask)
58        more : List<&2, W.Reply> <- answer.all(rest)
59        return W.Reply{ask, a} <> more
60
61# the World with more answers in it
62def more(world: W.World, rs: List<&2, W.Reply>) -> W.World:
63  W.World{paths, cwd, replies} = world
64  W.World{paths, cwd, List.append(&2, W.Reply, replies, rs)}
65
66# each line printed
67def print_all(lines: List<&2, String>) -> IO(Unit):  # noqa: L001 IO driver
68  match lines:
69    case Nil{}:
70      IO.pure(Unit, Unit{})
71    case Con{l, t}:
72      do IO<Unit>:
73        IO.print(l)
74        print_all(t)
75
76# a plan, carried out: its lines printed, then its exit status
77def exec(pl: Plan.Plan) -> IO(Unit):  # noqa: L001 IO driver
78  Plan.Plan{lines, exit} = pl
79  do IO<Unit>:
80    print_all(lines)
81    Status.stop(U32.is_gt(exit, 0))
82
83# the planner's questions answered and the loop gone round again, or, when
84# there are none, its plan carried out
85def loop.step(asks: List<&2, W.Ask>, +world: W.World, go: W.World -> IO(Unit)) -> IO(Unit):
86  match asks:
87    case Nil{}:
88      exec(Plan.plan(world))
89    case Con{a, rest}:
90      do IO<Unit>:
91        rs : List<&2, W.Reply> <- answer.all(a <> rest)
92        go(more(world, rs))
93
94# the loop, under fuel: the walk's bound, plus a round for the texts and one
95# to plan. Every round adds at least one answer, since the planner never asks
96# what the World answers already.
97def loop(fuel: Nat, +world: W.World) -> IO(Unit):  # noqa: L001 IO driver
98  match fuel:
99    case 0n:
100      IO.die(Unit, 1, "bolt: the lint kept asking")
101    case 1n+f:
102      loop.step(Plan.wants(world), world, ww => loop(f, ww))
103
104# the lint from a working directory; none that can be named is fatal, as no
105# relative path could be resolved against it
106def run.from(paths: List<&2, String>, +cwd: String) -> IO(Unit):
107  Lazy.stop(IO(Unit), String.is_empty(cwd), IO.die(Unit, 1, "bolt: cannot name the working directory"),
108    _u => loop(Nat.add(Plan.limit(), 2n), W.World{paths, cwd, []}))
109
110# the linter, over the files given, or the tree below
111def run(paths: List<&2, String>) -> IO(Unit):  # noqa: L001 IO driver
112  do IO<Unit>:
113    cwd : String <- WalkDisk.cwd()
114    run.from(paths, cwd)