File API

File reading and writing: whole-file helpers, streaming handles with typed binary I/O, and transparent compression and encryption.

Overview

The global identifier File is a namespace of static helpers and opener constructors. Opening a file returns a file handle whose instance methods are invoked as f.method(...). Static helpers cover whole-file reads and writes, metadata queries, and filesystem operations; handles provide streaming block, text, and typed binary I/O with per-handle or per-call endianness control.

Conventions

Paths. Strings are interpreted as filesystem paths, absolute or relative to the current working directory. No virtual-filesystem prefixes are applied.

Modes. The open* functions take a mode string:

ModeMeaning
"r"Read-only; the file must exist.
"w"Write; truncates or creates.
"rw"Read + write; the file must exist, position starts at 0.
"wr"Write + read; truncates or creates.

Numbers. Sizes, offsets, positions, byte values, and timestamps are Zym numbers. Integer methods truncate toward zero.

Buffers. Byte I/O uses Buffer instances.

Endianness. Typed multi-byte reads and writes accept an optional trailing "le" (default) or "be" string. See Endianness.

Open failures. open, openCompressed, openEncryptedPass, and openEncrypted return null on failure. Check with if f == null. Use lastError(), or a handle's getError(), for a numeric status. The whole-file helpers (File.readText / File.readBytes) instead raise a runtime error.

Errors. Invalid argument types, bad modes, negative sizes, or operations on a closed handle produce a Zym runtime error of the form File.method(args) ....

Opening Files

Four opener constructors return file handles, or null on failure. Compression and encryption support depends on the build.

File.open(path, mode)

Opens a file and returns a handle, or null on failure.

var f = File.open("data.bin", "r")
if (f == null) {
    print("open failed")
} else {
    // ... use the handle ...
    f.close()
}
File.openCompressed(path, mode, algo)

Opens a handle over a compressed stream, or returns null on failure. Availability of each algorithm depends on the build; an unsupported algorithm causes the open call to fail (null).

Brotli is decompress-only: the underlying engine ships a brotli decoder but no encoder, so "brotli" cannot be used with openCompressed for round-trip I/O. Opening for write ("w" / "wr") succeeds, but the close step fails with "Only brotli decompression is supported." and produces an empty file. Opening for read ("r" / "rw") only accepts streams in the engine's own framed compressed-file container, which the engine itself cannot produce, so there is currently no supported way to read a brotli stream through this API. Use one of the other algorithms for round-trip compression.
File.openEncryptedPass(path, mode, password)

Opens a handle over a password-encrypted stream, or returns null on failure. The encryption key is derived from password. Files written with openEncryptedPass can only be read back by openEncryptedPass with the same password.

File.openEncrypted(path, mode, key)

Opens a handle over an AES-256-CBC encrypted stream, or returns null on failure. A fresh 16-byte IV is generated on write and stored in the file header, so reads do not need it. Files written with openEncrypted can only be read back by openEncrypted with the same key.

Key management: use a high-entropy key, for example Crypto.create().generateRandomBytes(32), and store it separately; losing the key makes the file unrecoverable. openEncrypted and openEncryptedPass share an on-disk container format but differ in how the key is derived, so their files are not interchangeable. For in-memory encryption with a custom IV, use the AES native directly.

Whole-File Helpers

Convenience wrappers that open, transfer, and close in one call. The readers raise a runtime error on failure rather than returning null.

File.readBytes(path)

Reads the entire file into a new Buffer. Raises a runtime error on failure.

File.readText(path)

Reads the entire file as UTF-8 text. Raises a runtime error on failure.

File.writeBytes(path, buf)

Truncates or creates the file and writes the buffer. Returns a boolean.

File.writeText(path, s)

Truncates or creates the file and writes the string. Returns a boolean.

File.append(path, data)

Appends to an existing file, or creates one. Opens the file in read/write mode (falling back to write mode for new files), seeks to the end before writing, and closes the handle for you. Returns a boolean.

File.writeText("greeting.txt", "hello\n")
File.append("greeting.txt", "goodbye\n")
print("%s", File.readText("greeting.txt"))

Metadata

File.exists(path)

Returns true if a regular file exists at path.

File.size(path)

Returns the size in bytes, or 0 if the file is missing. Pair with exists to distinguish missing files from empty ones.

File.modifiedTime(path)

Returns the Unix timestamp (seconds) of the last modification. Returns 0 rather than raising when the path does not exist.

File.accessTime(path)

Returns the Unix timestamp (seconds) of the last access. Returns 0 rather than raising when the path does not exist.

File.md5(path)

Returns the MD5 hash of the file contents as lower-case hex. Streams the whole file, so it can be slow on large inputs.

File.sha256(path)

Returns the SHA-256 hash of the file contents as lower-case hex. Streams the whole file, so it can be slow on large inputs.

Filesystem Operations

File.copy(src, dst)

Copies a file. Returns true on success.

File.remove(path)

Deletes a file. Returns true on success.

File.rename(src, dst)

Renames or moves a file. Returns true on success.

Handle State & Positioning

f.isOpen()

Returns true while the handle holds an open file.

f.close()

Flushes and closes the handle. Safe to call more than once.

Closed-handle calls raise: after close(), calling any method other than isOpen(), close(), path(), pathAbsolute(), or getError() raises a runtime error.
f.path()

Returns the path as originally passed to the opener.

f.pathAbsolute()

Returns the absolute path.

f.length()

Returns the current file size in bytes.

f.position()

Returns the current cursor offset.

f.seek(pos)

Moves the cursor to the absolute byte offset pos.

f.seekEnd(off)

Moves the cursor to length() + off. Use 0 for end-of-file.

f.eof()

Returns true after a read has passed the last byte.

f.flush()

Flushes pending writes to disk.

f.resize(n)

Truncates or extends the file to n bytes. Returns a status code (0 on success). Newly exposed bytes on growth are not zero-filled on all platforms; write explicit zeros if you need a known state.

f.getError()

Returns the last error code observed on the handle (0 = ok).

f.setBigEndian(b)

Sets the handle-wide endianness default for typed I/O. See Endianness.

f.isBigEndian()

Returns the current handle-wide endianness setting.

var f = File.open("data.bin", "rw")
f.seekEnd(0)                   // jump to end-of-file
print("%n bytes", f.position())
f.seek(0)                      // back to the start
f.close()

Block I/O

f.readBytes(n)

Reads up to n bytes from the cursor into a new Buffer. The returned buffer may be shorter than n near end-of-file. Check buf.size() on the result rather than assuming the full count.

f.writeBytes(buf)

Writes the entire contents of buf at the cursor. Returns a boolean.

Text I/O

f.readText()

Reads the remaining bytes as UTF-8 text.

f.readLine()

Reads one line, up to and excluding the newline.

f.readCSVLine(delim?)

Parses one CSV row into a list of strings.

f.writeString(s)

Writes the raw UTF-8 bytes of s with no trailing newline. Returns a boolean.

f.writeLine(s)

Writes s followed by a newline. Returns a boolean.

f.writeCSVLine(list, delim?)

Writes list as a CSV row. All elements must be strings. Returns a boolean.

csv round trip
var out = File.open("table.csv", "w")
out.writeCSVLine(["name", "score"])
out.writeCSVLine(["alice", "98"])
out.close()

var src = File.open("table.csv", "r")
var header = src.readCSVLine()   // ["name", "score"]
var row = src.readCSVLine()      // ["alice", "98"]
src.close()

Integer Decode & Encode

Typed integer I/O at the cursor. Signed variants sign-extend; unsigned variants zero-extend. All multi-byte methods accept an optional trailing endian override e: "le" or "be". When omitted, the handle's current endian setting (see setBigEndian) is used. The 1-byte methods accept e for API symmetry but ignore it.

Precision gotcha: Zym numbers are IEEE 754 doubles, so only integers from −253 to 253 (±9,007,199,254,740,992) are represented exactly. U64 and I64 values beyond that range silently round on decode and lose low-order bits on encode. If you need exact 64-bit values, split them into two 32-bit halves with writeU32 / readU32.

Integer Reads

f.readU8(e?)

Reads an unsigned 8-bit integer (0–255).

f.readI8(e?)

Reads a signed 8-bit integer (−128 to 127).

f.readU16(e?)

Reads an unsigned 16-bit integer (0–65,535).

f.readI16(e?)

Reads a signed 16-bit integer (−32,768 to 32,767).

f.readU32(e?)

Reads an unsigned 32-bit integer (0–4,294,967,295).

f.readI32(e?)

Reads a signed 32-bit integer (−2,147,483,648 to 2,147,483,647).

f.readU64(e?)

Reads an unsigned 64-bit integer. Values outside the exact-integer range silently round on decode. See the precision note above.

f.readI64(e?)

Reads a signed 64-bit integer. Values outside the exact-integer range silently round on decode. See the precision note above.

Integer Writes

f.writeU8(v, e?)

Writes an unsigned 8-bit integer (0–255).

f.writeI8(v, e?)

Writes a signed 8-bit integer (−128 to 127).

f.writeU16(v, e?)

Writes an unsigned 16-bit integer.

f.writeI16(v, e?)

Writes a signed 16-bit integer.

f.writeU32(v, e?)

Writes an unsigned 32-bit integer.

f.writeI32(v, e?)

Writes a signed 32-bit integer.

f.writeU64(v, e?)

Writes an unsigned 64-bit integer. Values outside the exact-integer range lose low-order bits on encode. See the precision note above.

f.writeI64(v, e?)

Writes a signed 64-bit integer. Values outside the exact-integer range lose low-order bits on encode. See the precision note above.

typed writes
var f = File.open("data.bin", "w")
f.writeU32(0xCAFEBABE, "be")  // per-call big-endian
f.writeU16(1)                  // handle default (LE)
f.writeI8(-5)
f.close()

Float Decode & Encode

IEEE 754 floating-point I/O at the cursor. All float methods accept the same optional trailing endian override as the integer methods.

f.readHalf(e?)

Reads a 2-byte IEEE 754 half-precision float.

f.readFloat(e?)

Reads a 4-byte IEEE 754 single-precision float.

f.readDouble(e?)

Reads an 8-byte IEEE 754 double-precision float.

f.writeHalf(v, e?)

Writes a 2-byte IEEE 754 half-precision float. Narrows a Zym number (a double) to half precision; round-tripping is lossy for most values.

f.writeFloat(v, e?)

Writes a 4-byte IEEE 754 single-precision float. Narrows a Zym number (a double) to single precision; round-tripping is lossy for most values.

f.writeDouble(v, e?)

Writes an 8-byte IEEE 754 double-precision float.

Endianness

Typed multi-byte reads and writes on a file handle consult, in order:

  1. An explicit trailing "le" or "be" argument at the call site, if provided. The handle's setting is not modified.
  2. Otherwise, the handle-wide setting from setBigEndian(true|false) (default: little-endian).

Any string other than "le" / "be", or a non-string value in the endian slot, raises a runtime error.

Stateful: handle-wide endianness persists across calls. Mixing setBigEndian(true) with per-call "le" overrides is supported, but the per-call override does not change the handle's setting. Be explicit when interleaving.
override vs. handle default
var f = File.open("out.bin", "w")
f.writeU32(0x11223344, "be")   // per-call override
f.setBigEndian(true)
f.writeU32(0x55667788)         // uses handle default (BE)
f.close()

Examples

End-to-End File I/O

// Whole-file helpers
File.writeText("hello.txt", "hello\n")
print("%s", File.readText("hello.txt"))
print("size=%n exists=%b", File.size("hello.txt"), File.exists("hello.txt"))
print("sha=%s", File.sha256("hello.txt"))

// Streaming read
var f = File.open("hello.txt", "r")
if (f == null) {
    print("open failed")
} else {
    while (!f.eof()) {
        var line = f.readLine()
        if (line != "") {
            print("> %s", line)
        }
    }
    f.close()
}

// Binary write + seek
var g = File.open("out.bin", "w")
g.writeU32(0xCAFEBABE, "be")
g.writeFloat(3.14)
g.close()

File.remove("hello.txt")
File.remove("out.bin")