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. 12 import Base 13 import ./lint/world.bend as W 14 import ./lint/plan.bend as Plan 15 import ./lsp/files/disk.bend as Disk 16 import ./walk/disk.bend as WalkDisk 17 import ./status.bend as Status 18 import ./lazy/lazy.bend as Lazy 19 20 # a file's text as an answer 21 def 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 29 def 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: 35 mm : Maybe<&2, String> <- Disk.read(path) 36 return read.answer(mm) 37 38 # one question, answered 39 def answer(ask: W.Ask) -> IO(W.Answer): # noqa: L001 IO driver 40 match ask: 41 case W.Entries{dir}: 42 do IO: 43 es : List<&2, String> <- WalkDisk.entries(dir) 44 return W.Listed{es} 45 case W.Text{+path}: 46 do IO: 47 dir : Bool <- WalkDisk.is_dir(path) 48 text.answer(dir, path) 49 50 # every question, answered, in order 51 def 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>: 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 62 def 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 67 def 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: 73 IO.print(l) 74 print_all(t) 75 76 # a plan, carried out: its lines printed, then its exit status 77 def exec(pl: Plan.Plan) -> IO(Unit): # noqa: L001 IO driver 78 Plan.Plan{lines, exit} = pl 79 do IO: 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 85 def 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: 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. 97 def 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 106 def 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 111 def run(paths: List<&2, String>) -> IO(Unit): # noqa: L001 IO driver 112 do IO: 113 cwd : String <- WalkDisk.cwd() 114 run.from(paths, cwd)