Skip to main content

IO Library

The IO builtin library is AdeshLang's streaming subsystem: console I/O, in-memory readers/writers, buffered streams, binary serialization with configurable endianness, pipes, and stream adapters (tees, limits, counters). It is UTF-8 first, deterministic, and behaves identically on every backend (Interpreter, Bytecode VM, JIT, Native JIT, LLVM AOT, and WebAssembly).

The high-level API is written in AdeshLang itself (src/stdlib/IO.adesh) and sits on top of native io_* primitives (console read/write, byte packing/unpacking, UTF-8 conversion).

Classes

ClassWhat it provides
IOFacade with static helpers (IO.print, IO.println, IO.readLine, stream constructors, …)
Reader / WriterAbstract base interfaces for all streams
MemoryReader / MemoryWriterByte-backed in-memory streams (seekable)
BufferedReader / BufferedWriterChunked buffering for performance
BinaryReader / BinaryWriterFixed-width primitive serialization with endianness
TextReader / TextWriterText-oriented adapters over a reader/writer
ByteBuffer / StringBufferRaw byte buffer and string builder
Pipe / NullReader / NullWriterCommunication channel and discard/empty streams
TeeReader / TeeWriterDuplicate reads/writes across streams
LimitedReader / CountingReader / CountingWriterCap output / count bytes
StdinReader / StdoutWriter / StderrWriterConsole-backed streams
IOError / IOResultError type and ok/err result wrapper

Importing the library

Unlike Math/fs/JSON, the IO module is loaded from source — import it to make it available. The import is case-insensitive:

import IO; // or import io; or import "std:IO";
import { MemoryReader, IO } from "IO"; // selective import also works
note

Classes are accessed through the namespace (new IO.MemoryReader(...)), or bound directly with a selective import. Every stream implements the Reader/Writer method set, so adapters compose freely.


Methods: Console I/O

Print to stdout/stderr and read from stdin — the high-level, always-available entry points.

FunctionDescriptionReturns
IO.print(val)Write val to stdout (no newline)null
IO.println(val)Write val to stdout with a trailing newlinenull
IO.eprint(val)Write val to stderr (no newline)null
IO.eprintln(val)Write val to stderr with a trailing newlinenull
IO.readLine()Read one line from stdin (trailing newline stripped)string
IO.readChar()Read a single character from stdinstring
IO.stdin()A StdinReader for use with stream APIsStdinReader
IO.stdout()A StdoutWriter for use with stream APIsStdoutWriter
IO.stderr()A StderrWriter for use with stream APIsStderrWriter
import IO;

IO.println("Hello, AdeshLang!");
IO.eprintln("[log] wrote to stderr");
IO.print("No newline here -> ");

let name = IO.readLine(); // interactive
IO.println("Hi, " + name);

Interfaces: Reader & Writer

The base contracts implemented by every stream class.

Reader methods:

MethodDescription
.read(buf, off, length)Fill buf starting at off; returns bytes read
.readBytes(length)Read up to length bytes as an array
.readToEnd()Read everything remaining as a UTF-8 string
.readLine()Read the next line (without \n/\r\n); null at EOF
.readLines()Read all remaining lines into an array
.readUntil(delim)Read characters until (and including) delim

Writer methods:

MethodDescription
.write(buf, off, length)Write up to length bytes from buf; returns bytes written
.writeLine(s)Write s followed by a newline
.writeLines(lines)Write each line in the array
.flush()Force buffered data out

SeekableStream adds .seek(offset, whence) (whence: "start" / "current" / "end"), .rewind(), .skip(n), .position(), .length(), .remaining().


In-Memory Streams: MemoryReader & MemoryWriter

Work with bytes in memory without touching the filesystem — useful for testing, transforms, and buffering.

ClassConstructorKey methods
MemoryReader(data)data: string | array<u8>readBytes(n), readLine(), readLines(), readToEnd(), readUntil(delim), getBytes(), seek(), rewind(), position(), length(), remaining()
MemoryWriter()write(), writeLine(s), getBytes(), toString(), clear()
import IO;

let writer = new IO.MemoryWriter();
writer.writeLine("AdeshLang");
writer.writeLine("IO subsystem");

print(writer.toString()); // AdeshLang\nIO subsystem\n

let reader = new IO.MemoryReader(writer.toString());
while true {
let line = reader.readLine();
if line == null { break; }
print("-> " + line);
}

print(reader.remaining()); // 0 (fully consumed)
reader.rewind(); // back to the start
print(reader.readLine()); // AdeshLang

Buffered I/O: BufferedReader & BufferedWriter

Wrap any reader/writer with a fixed-size buffer to cut per-byte overhead.

ClassConstructorKey methods
BufferedReader(reader, capacity)any reader + buffer bytesreadBytes(n), readLine(), readLines(), readToEnd(), peek() (next byte without consuming), consume(n), clear()
BufferedWriter(writer, capacity)any writer + buffer byteswrite(), writeLine(s), flush(), clear()
import IO;

let mw = new MemoryWriter();
let bw = new BufferedWriter(mw, 64);
for i in 1..1000 {
bw.writeLine("record " + i);
}
bw.flush(); // write everything out in chunks

let br = new BufferedReader(new MemoryReader(mw.toString()), 32);
print(br.peek()); // first byte without consuming
print(len(br.readLines())); // 1000 lines read back
tip

Call flush() on a BufferedWriter when you're done (or when data must be visible downstream) — buffered writers hold data until the buffer fills.


Binary Serialization: BinaryReader & BinaryWriter

Fixed-width primitive serialization with explicit endianness ("little" default, or "big" / "BE").

BinaryWriter — all methods return the number of bytes written:

MethodWrites
writeByte(b) / writeBytes(bytes)Single byte / byte array
writeBool(v)1 byte (0/1)
writeChar(ch)UTF-8 encoded character
writeU8 / writeI81 byte
writeU16 / writeI162 bytes
writeU32 / writeI324 bytes
writeU64 / writeI648 bytes
writeF32 / writeF644 / 8 bytes (IEEE 754)
writeString(s)UTF-8 bytes of s
flush()Flush the underlying writer

BinaryReader — matching readU8readF64, plus:

MethodReturns
readByte()Next byte (0 at EOF)
readBytes(n)Next n bytes as an array
readBool()true/false from one byte
readChar()One UTF-8 character
readString(length)length bytes decoded as UTF-8
getBytes()Remaining bytes
import { MemoryWriter, MemoryReader, BinaryWriter, BinaryReader, IO } from "IO";

let mw = new MemoryWriter();
let bw = new BinaryWriter(mw, "big"); // big-endian packing

bw.writeU8(0xAB);
bw.writeU16(0x1234);
bw.writeU32(0x87654321);
bw.writeF64(2.718281828459);
bw.writeString("hello");
bw.flush();

let br = new BinaryReader(new MemoryReader(mw.getBytes()), "big");
print(br.readU8()); // 171
print(br.readU16()); // 4660
print(br.readU32()); // 2271560481
print(br.readF64()); // 2.718281828459
print(br.readString(5)); // hello
warning

The reader must be created with the same endianness as the writer, and readString(n) needs to know the exact byte length written. Mixing endianness or lengths produces garbage, not errors.


ByteBuffer & StringBuffer

Simple mutable buffers.

ClassConstructorKey methods
ByteBuffer(capacity)pre-sized raw byte buffersetEndianness(endian), writeByte(b), readByte() (-1 when exhausted), getBytes(), clear()
StringBuffer()string builderappend(s), appendLine(s) (both chainable), toString(), clear()
import IO;

let sb = new StringBuffer();
sb.append("A").append("B").appendLine("C");
print(sb.toString()); // ABC\n
sb.clear();

let bb = new ByteBuffer(4);
bb.setEndianness("big");
bb.writeByte(0xDE);
bb.writeByte(0xAD);
print(bb.getBytes()); // [222, 173]

Stream Adapters & Combinators

Wrap or combine streams to add behavior. All compose because they share the Reader/Writer interface.

Pipe & Null streams

Class / fnDescription
Pipe()Bounded in-memory reader-writer channel; write() returns -1 after close()
IO.pipe() / pipe()Returns [reader, writer] backed by one Pipe
NullReader()Always returns EOF (empty reads)
NullWriter()Discards writes (returns length)

Tee, Limit, Counting

ClassConstructorBehavior
TeeReader(reader, writer)source + sinkReads source fully, copying each chunk to the writer; then serves the same bytes back
TeeWriter(w1, w2)two writersWrites to both; returns the smaller byte count
LimitedReader(reader, n)source + max bytesServes at most n bytes
CountingReader(reader)sourceCounts bytes consumed; .count()
CountingWriter(writer)sinkCounts bytes written; .count()
import IO;

// Count bytes while copying:
let src = new MemoryReader("hello world");
let dst = new MemoryWriter();
let counted = new CountingWriter(dst);
IO.copy(src, counted);
print(counted.count()); // 11
print(dst.toString()); // hello world

// Cap how much is read:
let capped = new LimitedReader(new MemoryReader("0123456789"), 4);
print(capped.readToEnd()); // 0123

// Branch a write to two sinks:
let a = new MemoryWriter(); let b = new MemoryWriter();
let tee = new TeeWriter(a, b);
tee.write(io_utf8_encode("dup"), 0, 3);
print(a.toString() == b.toString()); // true

Combinator functions

Free functions available through the module: copy(src, dst), copyN(src, dst, n), pipe(), tee(reader, writer), limit(reader, n), repeat(byteVal, count), discard(reader), concat(readers), chain(readers). IO.* exposes the most common ones (IO.copy, IO.limit, IO.repeat, IO.concat, IO.pipe, IO.teeReader).

import IO;

// Repeat n bytes then read back:
let r = IO.repeat(65, 3); // 'A', 'A', 'A'
print(len(r.readToEnd())); // 3

// Concatenate several readers into one:
let combo = IO.concat([
new MemoryReader("one,"),
new MemoryReader("two,"),
new MemoryReader("three"),
]);
print(combo.readToEnd()); // one,two,three

Errors & Results: IOError / IOResult

ClassDescription
IOError(kind, message)Carries kind + message; toString()IOError[kind]: message
IOResult(val, err)Wrapper with IOResult.ok(val) / IOResult.err(kind, msg), .isOk(), .isErr(), .unwrap() (value or null), .error()

Complete example

A pipeline that writes text into memory, wraps it in a buffered reader, counts the lines, and reports metrics.

import IO;

print("--- IO stream pipeline ---");

let writer = new IO.MemoryWriter();
for i in 1..5 {
writer.writeLine("line-" + i);
}

let counting = IO.countingReader(new IO.MemoryReader(writer.toString()));
let lines = counting.readLines();

print("lines:", len(lines)); // 5
print("bytes consumed:", counting.count());

let sink = new IO.MemoryWriter();
let buffered = IO.bufferedWriter(sink, 16);
IO.copy(new IO.MemoryReader("stream it all"), buffered);
print("bytes written:", len(sink.toString())); // 14

Notes & edge cases

  • Import required: unlike Math/fs/JSON, use import IO; (or import "std:IO";) — the module is loaded from source (src/stdlib/IO.adesh).
  • UTF-8: all text streams are UTF-8. readLine/readLines strip \n (and \r\n). MemoryReader/BufferedReader decode bytes to characters during reads.
  • Endianness: binary packing defaults to little-endian; pass "big" (or "BE") to BinaryReader/BinaryWriter for network order. io_pack_number/io_unpack_number also accept "BE".
  • Buffers: call flush() on BufferedWriter/BinaryWriter before reading the underlying sink.
  • Binary reads return numbers: readU16readF64 return numeric values; readString(n) needs the exact byte count.
  • ByteBuffer.readByte() returns -1 when there are no more bytes; Pipe.write() returns -1 after close().
  • Backend parity: fully supported on Interpreter, Bytecode VM, JIT, Native JIT, LLVM AOT, and WebAssembly (the README matrix marks console/binary/in-memory streams YES everywhere).
  • Reader.readLine() returns null at EOF — always check for null before using the line.

Source & examples

  • High-level API: src/stdlib/IO.adesh
  • Native primitives: src/runtime/stdlib_src/io/ (stream_builtins.rs, file.rs)
  • Runnable examples: examples/Libraries/io/ (01_console_basic.adesh, 02_memory_stream.adesh, 03_binary_serialization.adesh, 04_buffered_io.adesh, 05_pipes_and_tees.adesh, 06_seeking.adesh, 07_counting.adesh, 08_limited.adesh)
  • Architecture & API reference (from the repo): examples/Libraries/io/Readme.md