Compare commits

...

10 Commits

Author SHA1 Message Date
Henrik Böving
118709ee6c chore: update stage0 2024-08-07 20:24:35 +02:00
Henrik Böving
c2575107f2 refactor: windows does not have getline 2024-08-07 19:25:18 +02:00
Henrik Böving
4db828d885 chore: move test 2024-08-07 19:25:18 +02:00
Henrik Böving
3f16f339e7 chore: use guard_msgs 2024-08-07 19:25:18 +02:00
Henrik Böving
2771296ca5 fix: keep trying to read after mdata size 2024-08-07 19:25:17 +02:00
Henrik Böving
5e337872ce test: NULL byte safety of Handle.putStr and Handle.getLine 2024-08-07 19:25:17 +02:00
Henrik Böving
329fa6309b fix: Handle.putStr for strings containing NULL bytes. 2024-08-07 19:25:17 +02:00
Henrik Böving
370488a9ff fix: Handle.getLine for lines containing NULL bytes 2024-08-07 19:25:17 +02:00
Henrik Böving
df38da8e09 feat: add lean_mk_string_from_bytes to object.h 2024-08-07 19:25:17 +02:00
Henrik Böving
a2b93d6c18 feat: read entire files in one system call 2024-08-07 19:25:17 +02:00
5 changed files with 65 additions and 44 deletions

View File

@@ -470,31 +470,23 @@ def withFile (fn : FilePath) (mode : Mode) (f : Handle → IO α) : IO α :=
def Handle.putStrLn (h : Handle) (s : String) : IO Unit :=
h.putStr (s.push '\n')
partial def Handle.readBinToEnd (h : Handle) : IO ByteArray := do
partial def Handle.readBinToEndInto (h : Handle) (buf : ByteArray) : IO ByteArray := do
let rec loop (acc : ByteArray) : IO ByteArray := do
let buf h.read 1024
if buf.isEmpty then
return acc
else
loop (acc ++ buf)
loop ByteArray.empty
loop buf
partial def Handle.readToEnd (h : Handle) : IO String := do
let rec loop (s : String) := do
let line h.getLine
if line.isEmpty then
return s
else
loop (s ++ line)
loop ""
partial def Handle.readBinToEnd (h : Handle) : IO ByteArray := do
h.readBinToEndInto .empty
def readBinFile (fname : FilePath) : IO ByteArray := do
let h Handle.mk fname Mode.read
h.readBinToEnd
def readFile (fname : FilePath) : IO String := do
let h Handle.mk fname Mode.read
h.readToEnd
def Handle.readToEnd (h : Handle) : IO String := do
let data h.readBinToEnd
match String.fromUTF8? data with
| some s => return s
| none => throw <| .userError s!"Tried to read from handle containing non UTF-8 data."
partial def lines (fname : FilePath) : IO (Array String) := do
let h Handle.mk fname Mode.read
@@ -600,6 +592,28 @@ end System.FilePath
namespace IO
namespace FS
def readBinFile (fname : FilePath) : IO ByteArray := do
-- Requires metadata so defined after metadata
let mdata fname.metadata
let size := mdata.byteSize.toUSize
let handle IO.FS.Handle.mk fname .read
let buf
if size > 0 then
handle.read mdata.byteSize.toUSize
else
pure <| ByteArray.mkEmpty 0
handle.readBinToEndInto buf
def readFile (fname : FilePath) : IO String := do
let data readBinFile fname
match String.fromUTF8? data with
| some s => return s
| none => throw <| .userError s!"Tried to read file '{fname}' containing non UTF-8 data."
end FS
def withStdin [Monad m] [MonadFinally m] [MonadLiftT BaseIO m] (h : FS.Stream) (x : m α) : m α := do
let prev setStdin h
try x finally discard <| setStdin prev

View File

@@ -485,43 +485,36 @@ extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_write(b_obj_arg h, b_obj_arg
}
}
/*
Handle.getLine : (@& Handle) → IO Unit
The line returned by `lean_io_prim_handle_get_line`
is truncated at the first '\0' character and the
rest of the line is discarded. */
/* Handle.getLine : (@& Handle) → IO Unit */
extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_get_line(b_obj_arg h, obj_arg /* w */) {
FILE * fp = io_get_handle(h);
const int buf_sz = 64;
char buf_str[buf_sz]; // NOLINT
std::string result;
bool first = true;
while (true) {
char * out = std::fgets(buf_str, buf_sz, fp);
if (out != nullptr) {
if (strlen(buf_str) < buf_sz-1 || buf_str[buf_sz-2] == '\n') {
if (first) {
return io_result_mk_ok(mk_string(out));
} else {
result.append(out);
return io_result_mk_ok(mk_string(result));
}
}
result.append(out);
} else if (std::feof(fp)) {
clearerr(fp);
return io_result_mk_ok(mk_string(result));
} else {
return io_result_mk_error(decode_io_error(errno, nullptr));
int c; // Note: int, not char, required to handle EOF
while ((c = std::fgetc(fp)) != EOF) {
result.push_back(c);
if (c == '\n') {
break;
}
first = false;
}
if (std::ferror(fp)) {
return io_result_mk_error(decode_io_error(errno, nullptr));
} else if (std::feof(fp)) {
clearerr(fp);
return io_result_mk_ok(mk_string(result));
} else {
obj_res ret = io_result_mk_ok(mk_string(result));
return ret;
}
}
/* Handle.putStr : (@& Handle) → (@& String) → IO Unit */
extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_put_str(b_obj_arg h, b_obj_arg s, obj_arg /* w */) {
FILE * fp = io_get_handle(h);
if (std::fputs(lean_string_cstr(s), fp) != EOF) {
usize n = lean_string_size(s) - 1; // - 1 to ignore the terminal NULL byte.
usize m = std::fwrite(lean_string_cstr(s), 1, n, fp);
if (m == n) {
return io_result_mk_ok(box(0));
} else {
return io_result_mk_error(decode_io_error(errno, nullptr));

Binary file not shown.

Binary file not shown.

14
tests/lean/run/3546.lean Normal file
View File

@@ -0,0 +1,14 @@
def test : IO Unit := do
let tmpFile := "3546.tmp"
let firstLine := "foo\u0000bar\n"
let content := firstLine ++ "hello world\nbye"
IO.FS.writeFile tmpFile content
let handle IO.FS.Handle.mk tmpFile .read
let firstReadLine handle.getLine
let cond := firstLine == firstReadLine && firstReadLine.length == 8 -- paranoid
IO.println cond
IO.FS.removeFile tmpFile
/-- info: true -/
#guard_msgs in
#eval test