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