1 # src/lint/world: everything `bolt` reads, as one value. The planner 2 # (src/lint/plan.bend) is a pure function of a World; the interpreter 3 # (src/lint.bend) gathers one, answering each question the planner asks by 4 # IO, and prints the plan the planner returns. The design is 5 # docs/rfc/bolt-lint-planner.md. 6 # 7 # The World holds only what the lint reads: the paths named on the command 8 # line, the working directory they are named from, and every answer the 9 # interpreter gave. A question with no answer yet reads as a directory with no 10 # entries or a file that cannot be read. 11 import Base 12 import ../lazy/lazy.bend as Lazy 13 14 # a question the planner asks. Each is answered by one IO action: a 15 # directory listed (src/walk/), or a file read (src/lsp/files/). 16 type Ask is Data: 17 Entries{dir: String} 18 Contents{path: String} 19 20 # the answer to one question. A directory that cannot be opened lists as 21 # empty, as src/walk/dir.c answers. A text asked of a path that is a 22 # directory is `Directory`, not a read: it has no text. 23 type Answer is Data: 24 Listed{names: List<&2, String>} 25 Read{text: String} 26 Unread{} 27 Directory{} 28 29 # one question, answered 30 type Reply is Data: 31 Reply{ask: Ask, answer: Answer} 32 33 # everything `bolt` reads: the paths named on the command line, the working 34 # directory (absolute) that relative paths are named from, and every answer 35 # the interpreter gave, in the order it gave them 36 type World is Data: 37 World{paths: List<&2, String>, cwd: String, replies: List<&2, Reply>} 38 39 # the paths named on the command line 40 def paths.of(world: World) -> List<&2, String>: 41 World{paths, _cwd, _replies} = world 42 paths 43 44 # the working directory the run was started in 45 def cwd.of(world: World) -> String: 46 World{_paths, cwd, _replies} = world 47 cwd 48 49 # whether two questions are the same question 50 def ask.eq(aa: Ask, bb: Ask) -> Bool: 51 match aa bb: 52 case Entries{x1} Entries{y1}: 53 String.eq(x1, y1) 54 case Contents{x2} Contents{y2}: 55 String.eq(x2, y2) 56 case Entries{_x3} Contents{_y3}: 57 False{} 58 case Contents{_x4} Entries{_y4}: 59 False{} 60 61 # the first answer given to a question 62 def answer.in(rs: List<&2, Reply>, +ask: Ask) -> Maybe<&2, Answer>: 63 match rs: 64 case Nil{}: 65 None{} 66 case Con{Reply{q, a}, rest}: 67 Lazy.stop(Maybe<&2, Answer>, ask.eq(q, ask), Some{a}, _u => answer.in(rest, ask)) 68 69 # the answer a World holds for a question, None while there is none 70 def answer(world: World, +ask: Ask) -> Maybe<&2, Answer>: 71 World{_paths, _cwd, rs} = world 72 answer.in(rs, ask) 73 74 # whether a World answers a question 75 def answered(world: World, +ask: Ask) -> Bool: 76 Maybe.is_some(&2, Answer, answer(world, ask)) 77 78 # a listing's names; anything else lists nothing 79 def listing.answer(aa: Answer) -> List<&2, String>: 80 match aa: 81 case Listed{names}: 82 names 83 case Read{_text}: 84 [] 85 case Unread{}: 86 [] 87 case Directory{}: 88 [] 89 90 def listing.got(mm: Maybe<&2, Answer>) -> List<&2, String>: 91 match mm: 92 case None{}: 93 [] 94 case Some{aa}: 95 listing.answer(aa) 96 97 # a directory's entries, a sub-directory's name ending in "/"; none while 98 # the directory is not listed yet 99 def listing(world: World, dir: String) -> List<&2, String>: 100 listing.got(answer(world, Entries{dir})) 101 102 # a read's text; anything else is no text 103 def text.answer(aa: Answer) -> Maybe<&2, String>: 104 match aa: 105 case Listed{_names}: 106 None{} 107 case Read{tt}: 108 Some{tt} 109 case Unread{}: 110 None{} 111 case Directory{}: 112 None{} 113 114 def text.got(mm: Maybe<&2, Answer>) -> Maybe<&2, String>: 115 match mm: 116 case None{}: 117 None{} 118 case Some{aa}: 119 text.answer(aa) 120 121 # a file's text, or None when it could not be read, is a directory, or is 122 # not read yet 123 def text(world: World, path: String) -> Maybe<&2, String>: 124 text.got(answer(world, Contents{path}))