src/lint/world.bend
bolt@1.17.0.0 · 124 lines · 0 definitions
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.
11import Base
12import bolt@1.17.0.0/src/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/).
16type 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.
23type Answer is Data:
24 Listed{names: List<&2, String>}
25 Read{text: String}
26 Unread{}
27 Directory{}
28
29# one question, answered
30type 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
36type World is Data:
37 World{paths: List<&2, String>, cwd: String, replies: List<&2, Reply>}
38
39# the paths named on the command line
40def 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
45def cwd.of(world: World) -> String:
46 World{_paths, cwd, _replies} = world
47 cwd
48
49# whether two questions are the same question
50def 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
62def 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
70def 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
75def 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
79def 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
90def 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
99def 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
103def 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
114def 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
123def text(world: World, path: String) -> Maybe<&2, String>:
124 text.got(answer(world, Contents{path}))