1 import Base 2 import ../lib/effs/io.bend as X 3 import ../lib/vstr.bend as S 4 import ./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 ---- 11 def 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 18 def read_done(r: File & Result<&1, &1, U32 & String, String>) -> IO(String): 19 match r: 20 case (f, got): 21 do IO: 22 File.close(f) 23 return X.out_of(got) 24 25 def read_sized(r: File & Result<&1, &1, U32 & String, U32>) -> IO(String): 26 match r: 27 case (f, n): 28 do IO: 29 got : File & Result<&1, &1, U32 & String, String> <- File.read(f, size_of(n)) 30 read_done(got) 31 32 def read_opened(r: Result<&1, &1, U32 & String, File>) -> IO(String): 33 match r: 34 case Done{f}: 35 do IO: 36 n : File & Result<&1, &1, U32 & String, U32> <- File.size(f) 37 read_sized(n) 38 case Fail{_}: 39 IO.pure(String, "") 40 41 def read_file(path: String) -> IO(String): 42 do IO: 43 r : Result<&1, &1, U32 & String, File> <- File.open(path, "r") 44 read_opened(r) 45 46 # ---- paths ---- 47 def 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 54 def can_pop(xs: List<&2, String>) -> Bool: 55 match xs: 56 case []: 57 False{} 58 case +h <> _: 59 Bool.not(String.eq(h, "..")) 60 61 def tail_of(xs: List<&2, String>) -> List<&2, String>: 62 match xs: 63 case []: 64 [] 65 case _ <> t: 66 t 67 68 def 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 71 def 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. 79 def norm(p: String) -> String: 80 String.join(List.reverse(&2, String, norm_go(String.split(p, '/'), [])), "/") 81 82 def dir_of(p: String) -> String: 83 String.join(List.reverse(&2, String, tail_of(List.reverse(&2, String, String.split(p, '/')))), "/") 84 85 # ---- imports ---- 86 def unquote(+w: String) -> String: 87 S.pick(String.starts_with(w, "\""), S.nth(String.split(w, '"'), 1n), w) 88 89 def import_of(+t: String) -> String: 90 S.pick(String.starts_with(t, "import "), unquote(S.nth(S.words(t), 1n)), "") 91 92 def relative(+p: String) -> Bool: 93 String.starts_with(p, "./") || String.starts_with(p, "../") 94 95 def 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. 99 def 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. 108 def 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 115 def 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>: 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 129 def 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 136 def list_text(xs: List<&2, String>) -> String: 137 "[" ++ S.quote_all(xs) ++ "]" 138 139 def 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 145 def write_sources(+f: P.Facts) -> IO(Unit): 146 do IO: 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))