src/lint.bend
bolt@1.11.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.11.0.0/src/lint/world.bend as W
14import bolt@1.11.0.0/src/lint/plan.bend as Plan
15import bolt@1.11.0.0/src/lsp/files/disk.bend as Disk
16import bolt@1.11.0.0/src/walk/disk.bend as WalkDisk
17import bolt@1.11.0.0/src/status.bend as Status
18import bolt@1.11.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.Text{+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)