module std::io; import libc; struct File (InStream, OutStream) { CFile file; } module std::io::file; import libc, std::io::path, std::io::os; fn File? open(String filename, String mode) { return from_handle(os::native_fopen(filename, mode)); } fn File? open_path(Path path, String mode) { return from_handle(os::native_fopen(path.str_view(), mode)); } fn bool exists(String file) => @pool() { return os::native_file_or_dir_exists(file); } fn File from_handle(CFile file) { return { .file = file }; } fn bool is_file(String path) { return os::native_is_file(path); } fn bool is_dir(String path) { return os::native_is_dir(path); } fn usz? get_size(String path) { return os::native_file_size(path); } fn void? delete(String filename) { return os::native_remove(filename) @inline; } <* @require self.file != null *> fn void? File.reopen(&self, String filename, String mode) { self.file = os::native_freopen(self.file, filename, mode)!; } <* @require self.file != null *> fn usz? File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic { os::native_fseek(self.file, offset, seek_mode)!; return os::native_ftell(self.file); } /* Implement later <* @require self.file == null *> fn void? File.memopen(File* file, char[] data, String mode) { @pool() { file.file = libc::memopen(data.ptr, data.len, mode.to_temp_zstr(), file.file); // TODO errors }; } */ <* @require self.file != null *> fn void? File.write_byte(&self, char c) @dynamic { return os::native_fputc(c, self.file); } <* @param [&inout] self *> fn void? File.close(&self) @inline @dynamic { if (self.file && libc::fclose(self.file)) { switch (libc::errno()) { case errno::ECONNRESET: case errno::EBADF: return io::FILE_NOT_VALID?; case errno::EINTR: return io::INTERRUPTED?; case errno::EDQUOT: case errno::EFAULT: case errno::EAGAIN: case errno::EFBIG: case errno::ENETDOWN: case errno::ENETUNREACH: case errno::ENOSPC: case errno::EIO: return io::INCOMPLETE_WRITE?; default: return io::UNKNOWN_ERROR?; } } self.file = null; } <* @require self.file != null *> fn bool File.eof(&self) @inline { return libc::feof(self.file) != 0; } <* @param [in] buffer *> fn usz? File.read(&self, char[] buffer) @dynamic { return os::native_fread(self.file, buffer); } <* @param [out] buffer @require self.file != null : `File must be initialized` *> fn usz? File.write(&self, char[] buffer) @dynamic { return os::native_fwrite(self.file, buffer); } fn Fd File.fd(self) @if(env::LIBC) { return libc::fileno(self.file); } fn bool File.isatty(self) @if(env::LIBC) { return libc::isatty(self.fd()) > 0; } fn char? File.read_byte(&self) @dynamic { int c = libc::fgetc(self.file); if (c == -1) return io::EOF?; return (char)c; } <* Load up to buffer.len characters. Returns io::OVERFLOW if the file is longer than the buffer. @param filename : "The path to the file to read" @param [in] buffer : "The buffer to read to" *> fn char[]? load_buffer(String filename, char[] buffer) { File file = open(filename, "rb")!; defer (void)file.close(); usz len = file.seek(0, END)!; if (len > buffer.len) return io::OVERFLOW?; file.seek(0, SET)!; usz read = 0; while (read < len) { read += file.read(buffer[read:len - read])!; } return buffer[:len]; } fn char[]? load(Allocator allocator, String filename) { File file = open(filename, "rb")!; defer (void)file.close(); usz len = file.seek(0, END)!; file.seek(0, SET)!; char* data = allocator::malloc_try(allocator, len)!; defer catch allocator::free(allocator, data); usz read = 0; while (read < len) { read += file.read(data[read:len - read])!; } return data[:len]; } fn char[]? load_path(Allocator allocator, Path path) => load(allocator, path.str_view()); fn char[]? load_temp(String filename) => load(tmem, filename); fn char[]? load_path_temp(Path path) => load_temp(path.str_view()); fn void? save(String filename, char[] data) { File file = open(filename, "wb")!; defer (void)file.close(); while (data.len) { usz written = file.write(data)!; data = data[written..]; } } <* @require self.file != null : `File must be initialized` *> fn void? File.flush(&self) @dynamic { libc::fflush(self.file); }