Lcl · Core · Packages · Libraries


lcl-io

File I/O for Lcl, bound to the ANSI C standard library and nothing else.

Requirements

Build

cmake -S . -B build -DLCL_BUILD_IO=ON
cmake --build build

Usage

Whole-file operations take a path and do everything in one call; the streaming operations work on an opaque file handle returned by Io::open_file or one of the standard-stream accessors (Io::stdin, Io::stdout, Io::stderr).

;; Read an entire file
let contents [Io::read_file "config.txt"]

;; Write (overwrite) a file
Io::write_file output.txt "Hello, World!"

;; Stream-based I/O
let f [Io::open_file data.txt r]

while 1 {
    let line [Io::fgets $f 1024]
    if [== [String::length $line] 0] { break }
    puts $line
}
Io::close_file $f

Most procs return the empty string on success and raise an error on failure. Only Io::read_file and Io::write_file attach a descriptive message; the rest raise a bare error (the message reads unknown error), so wrap them in catch when the failure mode matters to you.

For plain writes to standard output use puts from the core stdlib; Io::stdout exists for when you need a handle (for Io::fputs without a trailing newline, or Io::flush).

namespace Io

Procs are listed in groups: whole-file operations (Io::read_file, Io::write_file, Io::copy, Io::remove, Io::rename), streaming I/O on file handles (Io::open_file, Io::close_file, Io::fgets, Io::fputs, Io::flush, Io::eof?), the standard streams (Io::stdin, Io::stdout, Io::stderr) and the environment (Io::getenv).

A file handle is an opaque value wrapping a C FILE *. Handles are not closed automatically: pair every Io::open_file with Io::close_file.

proc Io::read_file path

Return the entire contents of the file at path as a string.

The file is opened in binary mode and read in full. A file that cannot be opened or read raises an error naming the path.

Examples:

>> String::range [Io::read_file LICENSE] 0 11
"MIT License"
>> Io::read_file no/such/file.txt
!! could not read
>> Io::read_file
!! expected 1 argument

proc Io::write_file path data

Write data to the file at path, creating or truncating it.

data is written as bytes (binary mode); nothing is appended. Raises an error if the file cannot be opened for writing or the write is short. Returns the empty string.

Io::write_file notes.txt "first line\nsecond line\n"

Examples:

>> Io::write_file only-a-path
!! expected 2 arguments

proc Io::copy src dst

Copy the contents of the file at src to dst, creating or truncating dst.

Bytes are copied through an 8 KiB buffer; no metadata (permissions, timestamps) is preserved. Raises an error if either file cannot be opened or a write is short. Returns the empty string.

Io::copy config.txt config.txt.bak

proc Io::remove path

Delete the file at path.

Wraps C remove; on most platforms it also removes an empty directory, but use Posix::rmdir when you mean that. Raises an error if the file does not exist or cannot be removed. Returns the empty string.

Io::remove scratch.tmp

proc Io::rename old new

Rename (move) the file or directory at old to new.

Wraps C rename, so the same rules apply: whether an existing new is replaced and whether moves cross filesystems are up to the platform. Raises an error on failure. Returns the empty string.

Io::rename output.tmp output.txt

proc Io::open_file path mode

Open the file at path and return a file handle.

mode is passed straight to C fopen: r, w, a, r+, w+, a+, optionally with b for binary (rb, wb, …). Raises an error if the file cannot be opened.

let f [Io::open_file log.txt a]
Io::fputs $f "appended\n"
Io::close_file $f

proc Io::close_file handle

Close a file handle returned by Io::open_file.

Closing one of the standard streams (Io::stdin, Io::stdout, Io::stderr) is a no-op. Raises an error if the underlying close fails. Returns the empty string.

Examples:

>> Io::close_file [Io::stdout]
""

proc Io::fgets handle size

Read one line from handle, up to size - 1 bytes.

Behaves like C fgets: the trailing newline is kept, so a blank line reads as "\n" and an empty result always means end-of-file (or a line longer than size - 1 bytes, which is returned in pieces). size must be at least 2. A read error raises an error; end-of-file never does – check Io::eof? or the length of the result.

let f [Io::open_file data.txt r]
var lines ()
while 1 {
    let line [Io::fgets $f 4096]
    if [== [String::length $line] 0] { break }
    List::push! lines [String::trim $line]
}
Io::close_file $f

proc Io::fputs handle data

Write the string data to handle, with no newline added.

Raises an error if the write fails. Returns the empty string.

Io::fputs [Io::stderr] "warning: no config found\n"

proc Io::flush handle

Flush any buffered output on handle.

Raises an error if the flush fails. Returns the empty string.

Io::fputs [Io::stdout] "progress: "
Io::flush [Io::stdout]

proc Io::eof? handle

Return 1 if handle has hit end-of-file, else 0.

The flag is set by a read that reaches the end, not by position: a freshly opened empty file reports 0 until the first Io::fgets returns "".

Examples:

>> Io::eof? [Io::stdout]
0

proc Io::stdin

Return a file handle for the process’s standard input.

let line [Io::fgets [Io::stdin] 256]

proc Io::stdout

Return a file handle for the process’s standard output.

Examples:

>> type [Io::stdout]
"opaque"

proc Io::stderr

Return a file handle for the process’s standard error.

Io::fputs [Io::stderr] "fatal: $msg\n"

proc Io::getenv name

Return the value of the environment variable name, or the empty string if it is not set.

An unset variable and one set to the empty string are indistinguishable.

Examples:

>> Io::getenv LCL_NO_SUCH_VARIABLE_XYZ
""