1 # io/file: a whole file read or written in one call. Base gives an open handle 2 # and chunked reads; this is the loop over them. 3 import Base 4 5 # the handle closed, and whatever was gathered 6 def slurp.end(file: File, acc: String) -> IO(String): 7 do IO: 8 File.close(file) 9 return acc 10 11 # an empty chunk means the end of the file 12 def slurp.cut(chunk: String, file: File, acc: String, rest: File -> String -> IO(String)) -> IO(String): 13 match chunk: 14 case SNil{}: 15 slurp.end(file, acc) 16 case SCon{c, t}: 17 rest(file, acc ++ SCon{c, t}) 18 19 # a failed read ends the file as surely as an empty one 20 def slurp.more( 21 res: Result<&1, &1, U32 & String, String>, 22 file: File, 23 acc: String, 24 rest: File -> String -> IO(String) 25 ) -> IO(String): 26 match res: 27 case Fail{_e}: 28 slurp.end(file, acc) 29 case Done{chunk}: 30 slurp.cut(chunk, file, acc, rest) 31 32 # the handle comes back paired with what it read 33 def slurp.next( 34 got: File & Result<&1, &1, U32 & String, String>, 35 acc: String, 36 rest: File -> String -> IO(String) 37 ) -> IO(String): 38 (file, r) = got 39 slurp.more(r, file, acc, rest) 40 41 # the whole file, read in chunks until one is empty, or the fuel is 42 def slurp(fuel: Nat, file: File, acc: String) -> IO(String): 43 match fuel: 44 case 0n: 45 slurp.end(file, acc) 46 case 1n+f: 47 do IO: 48 got : File & Result<&1, &1, U32 & String, String> <- File.read(file, 65536) 49 slurp.next(got, acc, fl => a => slurp(f, fl, a)) 50 51 # the reads a file with no size to go by may take: a pipe, a device or a 52 # /proc file reports 0 bytes and a file past 4 GiB fails to report, so these 53 # are read until they end, up to 100000 reads of 64 KiB, past any file ez reads 54 def unsized() -> Nat: 55 U32.to_nat(100000) 56 57 # a size of 0 is no size to go by; any other is read to 58 def reads.given(none: Bool, bytes: U32) -> Nat: 59 match none: 60 case True{}: 61 unsized() 62 case False{}: 63 U32.to_nat(U32.add(U32.div(bytes, 65536), 2)) 64 65 # the reads a file of this many bytes takes: one for every 64 KiB, one for 66 # the part past the last whole 64 KiB, and one to spare for a short read 67 def reads(+bytes: U32) -> Nat: 68 reads.given(U32.is_zero(bytes), bytes) 69 70 # the reads the size the host reported allows 71 def reads.of(res: Result<&1, &1, U32 & String, U32>) -> Nat: 72 match res: 73 case Fail{_e}: 74 unsized() 75 case Done{bytes}: 76 reads(bytes) 77 78 # a file read whole, with fuel enough for the size it reported 79 def read.sized(got: File & Result<&1, &1, U32 & String, U32>) -> IO(String): 80 (file, r) = got 81 slurp(reads.of(r), file, "") 82 83 # what an opened file holds, or nothing when it would not open 84 def read.opened(res: Result<&1, &1, U32 & String, File>) -> IO(Maybe<&2, String>): 85 match res: 86 case Fail{_e}: 87 IO.pure(Maybe<&2, String>, None{}) 88 case Done{file}: 89 do IO>: 90 got : File & Result<&1, &1, U32 & String, U32> <- File.size(file) 91 text : String <- read.sized(got) 92 return Some{text} 93 94 # a file's text, or None when it cannot be opened 95 def read(path: String) -> IO(Maybe<&2, String>): 96 do IO>: 97 r : Result<&1, &1, U32 & String, File> <- File.open(path, "r") 98 read.opened(r) 99 100 # whether a write reported success 101 def write.ok(res: Result<&1, &1, U32 & String, Unit>) -> Bool: 102 match res: 103 case Fail{_e}: 104 False{} 105 case Done{_u}: 106 True{} 107 108 # a write's outcome, once the handle is shut 109 def write.done(written: File & Result<&1, &1, U32 & String, Unit>) -> IO(Bool): 110 (file, r) = written 111 do IO: 112 File.close(file) 113 return write.ok(r) 114 115 # the text written to an opened file 116 def write.opened(res: Result<&1, &1, U32 & String, File>, text: String) -> IO(Bool): 117 match res: 118 case Fail{_e}: 119 IO.pure(Bool, False{}) 120 case Done{file}: 121 do IO: 122 w : File & Result<&1, &1, U32 & String, Unit> <- File.write(file, text) 123 write.done(w) 124 125 # a file replaced with this text; False when it could not be written 126 def write(path: String, text: String) -> IO(Bool): 127 do IO: 128 r : Result<&1, &1, U32 & String, File> <- File.open(path, "w") 129 write.opened(r, text) 130 131 # a file's text, or "" when there is none 132 def text_of(got: Maybe<&2, String>) -> String: 133 match got: 134 case None{}: 135 "" 136 case Some{s}: 137 s