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.

FunctionSignatureDescription
printlnstr → voidPrint string + newline to stdout
eprintstr → voidPrint string to stderr
eprintlnstr → voidPrint string + newline to stderr

Note: print(s) and input(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:

FunctionSignatureDescription
read_allstr → cstrRead entire file as a null-terminated string
write_all(str, str) → boolWrite string to file (creates/overwrites)
append_all(str, str) → boolAppend 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:

FunctionSignatureDescription
file_open(str, str) → *voidOpen file with mode ("r", "w", "rb", etc.)
file_close*void → boolClose file handle
file_read(*void, *void, i64) → i64Read bytes into buffer, returns count read
file_write(*void, *void, i64) → i64Write bytes from buffer, returns count written
file_seek(*void, i64, i32) → boolSeek to offset from origin
file_tell*void → i64Get current position
file_eof*void → boolCheck if at end of file
file_flush*void → boolFlush buffered writes

File handles are *void (wrapping C’s FILE*). Returns null (0) on failure.

Seek Constants

ConstantValueDescription
FILE_SEEK_SET0Seek from beginning of file
FILE_SEEK_CUR1Seek from current position
FILE_SEEK_END2Seek 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.

FunctionSignatureDescription
existsstr → boolCheck if path exists (file or directory)
is_filestr → boolCheck if path is a regular file
is_dirstr → boolCheck if path is a directory
file_sizestr → i64Get file size in bytes (returns -1 on failure)
remove_filestr → boolDelete a file
rename_file(str, str) → boolRename or move a file
make_dirstr → boolCreate a directory
remove_dirstr → boolRemove an empty directory

Naming: remove_file, rename_file, make_dir, and remove_dir are 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.

FunctionSignatureDescription
dirnamestr → strDirectory part: "foo/bar.txt""foo"
basenamestr → strFilename part: "foo/bar.txt""bar.txt"
extensionstr → strExtension without dot: "bar.txt""txt"
stemstr → strFilename without extension: "bar.txt""bar"
is_absolutestr → boolCheck if path is absolute (/ or C:\)
join(str, str) → strJoin with /: "foo" + "bar""foo/bar"

Helper functions (used internally but also available):

FunctionSignatureDescription
is_sepu8 → boolCheck if byte is / or \
last_sepstr → i64Position of last separator (-1 if none)

Edge Cases

Inputdirnamebasenameextensionstem
"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.fs uses _stat64 and _mkdir/_rmdir. io.file uses _fseeki64/_ftelli64 (except with TCC).
  • Linux/macOS: io.fs uses POSIX stat and mkdir/rmdir. io.file uses fseeko/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