IO Module
GX includes a cross-platform IO module for console output, file operations, filesystem queries, and path manipulation. It wraps portable C functions via thin C helper headers and provides clean GX wrappers on top.
Usage
Import individual sub-modules:
import io.io // println, eprint, eprintln
import io.file // file open/close/read/write, read_all, write_all
import io.fs // exists, is_file, is_dir, mkdir, remove
import io.path // dirname, basename, extension, join
Build with -I modules so the compiler finds the modules/io/gx/ directory:
gx myapp.gx -I modules -o myapp.exe
Console IO (io.io)
Extends the built-in print(s) and input(prompt) functions with newline
and stderr variants.
| Function | Signature | Description |
|---|---|---|
println | str → void | Print string + newline to stdout |
eprint | str → void | Print string to stderr |
eprintln | str → void | Print string + newline to stderr |
Note:
print(s)andinput(prompt)are built-in and always available without importing any module.
Example
import io.io
fn main() {
println("Hello, world!")
eprintln("Something went wrong")
var name = input("Enter your name: ")
println("Hi, {name}!")
}
File IO (io.file)
Provides both convenience functions (read_all, write_all) for common
one-shot operations and a low-level handle-based API for fine-grained control.
Convenience Functions
These cover the most common use cases — reading/writing entire files in a single call:
| Function | Signature | Description |
|---|---|---|
read_all | str → cstr | Read entire file as a null-terminated string |
write_all | (str, str) → bool | Write string to file (creates/overwrites) |
append_all | (str, str) → bool | Append string to end of file |
read_all returns cstr (not str) because the C helper allocates a new
buffer. The caller is responsible for freeing it. When passed to functions
expecting str, GX auto-wraps it via ec_str_make().
Example: Read and Write Files
import io.io
import io.file
fn main() {
// Write a file
write_all("output.txt", "Hello from GX!\nLine 2\n")
// Read it back
var content = read_all("output.txt")
print(content)
// Append a line
append_all("output.txt", "Line 3\n")
}
Handle-Based API
For reading/writing in chunks, seeking, or working with binary data:
| Function | Signature | Description |
|---|---|---|
file_open | (str, str) → *void | Open file with mode ("r", "w", "rb", etc.) |
file_close | *void → bool | Close file handle |
file_read | (*void, *void, i64) → i64 | Read bytes into buffer, returns count read |
file_write | (*void, *void, i64) → i64 | Write bytes from buffer, returns count written |
file_seek | (*void, i64, i32) → bool | Seek to offset from origin |
file_tell | *void → i64 | Get current position |
file_eof | *void → bool | Check if at end of file |
file_flush | *void → bool | Flush buffered writes |
File handles are *void (wrapping C’s FILE*). Returns null (0) on failure.
Seek Constants
| Constant | Value | Description |
|---|---|---|
FILE_SEEK_SET | 0 | Seek from beginning of file |
FILE_SEEK_CUR | 1 | Seek from current position |
FILE_SEEK_END | 2 | Seek from end of file |
Example: Low-Level File IO
import io.io
import io.file
fn main() {
var f = file_open("data.bin", "rb")
if (f == 0) {
eprintln("Failed to open file")
return
}
// Seek to end to get file size
file_seek(f, 0, FILE_SEEK_END)
var size = file_tell(f)
println("File size: {size} bytes")
// Seek back to start
file_seek(f, 0, FILE_SEEK_SET)
// Read first 64 bytes
var buf: u8[64]
var n = file_read(f, &buf, 64)
println("Read {n} bytes")
file_close(f)
}
Filesystem (io.fs)
Cross-platform filesystem queries and operations. Uses stat/_stat64 on
Windows, POSIX stat elsewhere. All functions take str paths.
| Function | Signature | Description |
|---|---|---|
exists | str → bool | Check if path exists (file or directory) |
is_file | str → bool | Check if path is a regular file |
is_dir | str → bool | Check if path is a directory |
file_size | str → i64 | Get file size in bytes (returns -1 on failure) |
remove_file | str → bool | Delete a file |
rename_file | (str, str) → bool | Rename or move a file |
make_dir | str → bool | Create a directory |
remove_dir | str → bool | Remove an empty directory |
Naming:
remove_file,rename_file,make_dir, andremove_dirare prefixed to avoid conflicts with C standard library functions (remove,rename,mkdir,rmdir).
Example
import io.io
import io.fs
import io.file
fn main() {
// Check before reading
if (!exists("config.txt")) {
eprintln("config.txt not found")
return
}
if (is_file("config.txt")) {
var sz = file_size("config.txt")
println("config.txt is {sz} bytes")
}
// Create a temp directory, write a file, clean up
make_dir("tmp")
write_all("tmp/data.txt", "temporary data")
if (is_dir("tmp")) {
remove_file("tmp/data.txt")
remove_dir("tmp")
}
}
Path Utilities (io.path)
Pure string operations for manipulating file paths. Handles both / (Unix)
and \ (Windows) separators. No C dependencies — implemented entirely in GX.
| Function | Signature | Description |
|---|---|---|
dirname | str → str | Directory part: "foo/bar.txt" → "foo" |
basename | str → str | Filename part: "foo/bar.txt" → "bar.txt" |
extension | str → str | Extension without dot: "bar.txt" → "txt" |
stem | str → str | Filename without extension: "bar.txt" → "bar" |
is_absolute | str → bool | Check if path is absolute (/ or C:\) |
join | (str, str) → str | Join with /: "foo" + "bar" → "foo/bar" |
Helper functions (used internally but also available):
| Function | Signature | Description |
|---|---|---|
is_sep | u8 → bool | Check if byte is / or \ |
last_sep | str → i64 | Position of last separator (-1 if none) |
Edge Cases
| Input | dirname | basename | extension | stem |
|---|---|---|---|---|
"foo/bar.txt" | "foo" | "bar.txt" | "txt" | "bar" |
"C:\Users\test\file.txt" | "C:\Users\test" | "file.txt" | "txt" | "file" |
"/foo/bar" | "/foo" | "bar" | "" | "bar" |
"bar.txt" | "" | "bar.txt" | "txt" | "bar" |
"archive.tar.gz" | "" | "archive.tar.gz" | "gz" | "archive.tar" |
"Makefile" | "" | "Makefile" | "" | "Makefile" |
"/" | "/" | "" | "" | "" |
Example
import io.io
import io.path
fn main() {
var p = "src/frontend/parser/parser.cpp"
println("dir: {dirname(p)}") // src/frontend/parser
println("file: {basename(p)}") // parser.cpp
println("ext: {extension(p)}") // cpp
println("stem: {stem(p)}") // parser
var out = join("build", "output.exe")
println("joined: {out}") // build/output.exe
if (is_absolute("/usr/bin")) {
println("absolute path")
}
}
Platform Notes
- All modules work with both TCC and clang/gcc. TCC uses 32-bit
fseek/ftell(files up to 2 GB); clang/gcc use 64-bit variants for large file support. - Windows:
io.fsuses_stat64and_mkdir/_rmdir.io.fileuses_fseeki64/_ftelli64(except with TCC). - Linux/macOS:
io.fsuses POSIXstatandmkdir/rmdir.io.fileusesfseeko/ftello. - io.path is pure GX with zero platform-specific code. It handles both
/and\separators on all platforms.
Module Layout
modules/
io/
gx/
io.gx println, eprint, eprintln
file.gx file_open/close/read/write, read_all, write_all, append_all
fs.gx exists, is_file, is_dir, file_size, remove_file, make_dir, etc.
path.gx dirname, basename, extension, stem, join, is_absolute
c/
io_console.h stderr output helpers (fputs to stderr)
io_file.h FILE* wrappers with TCC/MSVC/POSIX compat
io_fs.h stat/mkdir/rmdir with cross-platform #ifdef