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))