src/file.bend

snap@1.4.0.0 · 105 lines · 13 definitions

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