cli/vsources.bend
near-v-framework@0.2.0.0 · 154 lines · 0 definitions
1import Base
2import near-v-framework@0.2.0.0/lib/effs/io.bend as X
3import near-v-framework@0.2.0.0/lib/vstr.bend as S
4import near-v-framework@0.2.0.0/cli/project.bend as P
5
6# The repository's files and what its packages ship, read in Bend for the no-dead-code and Bend-only
7# laws. Listing the files is the one command it runs (`git ls-files`); every file is read with Bend's
8# File API and every import is followed in Bend.
9
10# ---- reading a file ----
11def size_of(r: Result<&1, &1, U32 & String, U32>) -> U32:
12 match r:
13 case Done{n}:
14 n
15 case Fail{_}:
16 0
17
18def read_done(r: File & Result<&1, &1, U32 & String, String>) -> IO(String):
19 match r:
20 case (f, got):
21 do IO<String>:
22 File.close(f)
23 return X.out_of(got)
24
25def read_sized(r: File & Result<&1, &1, U32 & String, U32>) -> IO(String):
26 match r:
27 case (f, n):
28 do IO<String>:
29 got : File & Result<&1, &1, U32 & String, String> <- File.read(f, size_of(n))
30 read_done(got)
31
32def read_opened(r: Result<&1, &1, U32 & String, File>) -> IO(String):
33 match r:
34 case Done{f}:
35 do IO<String>:
36 n : File & Result<&1, &1, U32 & String, U32> <- File.size(f)
37 read_sized(n)
38 case Fail{_}:
39 IO.pure(String, "")
40
41def read_file(path: String) -> IO(String):
42 do IO<String>:
43 r : Result<&1, &1, U32 & String, File> <- File.open(path, "r")
44 read_opened(r)
45
46# ---- paths ----
47def choose(c: Bool, a: List<&2, String>, b: List<&2, String>) -> List<&2, String>:
48 match c:
49 case True{}:
50 a
51 case False{}:
52 b
53
54def can_pop(xs: List<&2, String>) -> Bool:
55 match xs:
56 case []:
57 False{}
58 case +h <> _:
59 Bool.not(String.eq(h, ".."))
60
61def tail_of(xs: List<&2, String>) -> List<&2, String>:
62 match xs:
63 case []:
64 []
65 case _ <> t:
66 t
67
68def norm_step(+seg: String, +stack: List<&2, String>) -> List<&2, String>:
69 choose(String.eq(seg, "") || String.eq(seg, "."), stack, choose(String.eq(seg, "..") && can_pop(stack), tail_of(stack), seg <> stack))
70
71def norm_go(segs: List<&2, String>, +stack: List<&2, String>) -> List<&2, String>:
72 match segs:
73 case []:
74 stack
75 case +s <> rest:
76 norm_go(rest, norm_step(s, stack))
77
78# A path with "." and ".." resolved.
79def norm(p: String) -> String:
80 String.join(List.reverse(&2, String, norm_go(String.split(p, '/'), [])), "/")
81
82def dir_of(p: String) -> String:
83 String.join(List.reverse(&2, String, tail_of(List.reverse(&2, String, String.split(p, '/')))), "/")
84
85# ---- imports ----
86def unquote(+w: String) -> String:
87 S.pick(String.starts_with(w, "\""), S.nth(String.split(w, '"'), 1n), w)
88
89def import_of(+t: String) -> String:
90 S.pick(String.starts_with(t, "import "), unquote(S.nth(S.words(t), 1n)), "")
91
92def relative(+p: String) -> Bool:
93 String.starts_with(p, "./") || String.starts_with(p, "../")
94
95def keep_import(+p: String, +dir: String, +rest: List<&2, String>) -> List<&2, String>:
96 choose(relative(p), norm(dir ++ "/" ++ p) <> rest, rest)
97
98# The repository files a file imports (Bend modules and C effects), relative to the repository.
99def imports_of(ls: List<&2, String>, +dir: String) -> List<&2, String>:
100 match ls:
101 case []:
102 []
103 case l <> rest:
104 keep_import(import_of(String.trim(l)), dir, imports_of(rest, dir))
105
106# ---- what the packages ship: each entry and everything it imports ----
107# Each step handles one import, repeats included; files squared bounds them.
108def seen_in(+x: String, ys: List<&2, String>) -> Bool:
109 match ys:
110 case []:
111 False{}
112 case y <> rest:
113 String.eq(x, y) || seen_in(x, rest)
114
115def ship(fuel: Nat, +todo: List<&2, String>, +seen: List<&2, String>, +root: String) -> IO(List<&2, String>):
116 match fuel:
117 case 0n:
118 IO.pure(List<&2, String>, seen)
119 case 1n+k:
120 match todo:
121 case []:
122 IO.pure(List<&2, String>, seen)
123 case +f <> rest:
124 do IO<List<&2, String>>:
125 text : String <- read_file(root ++ "/" ++ f)
126 +known : Bool = seen_in(f, seen)
127 ship(k, S.append(rest, choose(known, [], imports_of(String.lines(text), dir_of(f)))), choose(known, seen, S.append(seen, [f])), root)
128
129def entries(ps: List<&2, String>, +project: String) -> List<&2, String>:
130 match ps:
131 case []:
132 []
133 case +p <> rest:
134 norm(project ++ "/" ++ S.nth(S.words(p), 2n)) <> entries(rest, project)
135
136def list_text(xs: List<&2, String>) -> String:
137 "[" ++ S.quote_all(xs) ++ "]"
138
139def sources_text(+vlib: String, +files: List<&2, String>, +project: String, +shipped: List<&2, String>) -> String:
140 "import Base\n\n# The repository's files, this project's folder and the files its packages ship, for the\n# no-dead-code and Bend-only laws. Written by `v system check`; do not edit.\n" ++
141 "def all_files() -> List<&2, String>:\n " ++ list_text(files) ++ "\n\n" ++
142 "def project() -> String:\n \"" ++ project ++ "\"\n\n" ++
143 "def shipped() -> List<&2, String>:\n " ++ list_text(shipped) ++ "\n"
144
145def write_sources(+f: P.Facts) -> IO(Unit):
146 do IO<Unit>:
147 +root : String <- X.sh("git rev-parse --show-toplevel")
148 +here : String <- X.sh("pwd")
149 +listing : String <- X.sh("git -C '" ++ String.trim(root) ++ "' ls-files -co --exclude-standard")
150 +files : List<&2, String> = S.nonempty(String.lines(listing))
151 +project : String = String.drop(String.trim(here), Nat.add(1n, String.length(String.trim(root))))
152 +steps : Nat = Nat.add(1n, List.length(&2, String, files))
153 +shipped : List<&2, String> <- ship(Nat.mul(steps, steps), entries(P.packages_of(f), project), [], String.trim(root))
154 X.write_file("sources.bend", sources_text(P.vlib_of(f), files, project, shipped))