module std::io; import std::math; interface InStream { fn void? close() @optional; fn usz? seek(isz offset, Seek seek) @optional; fn usz len() @optional; fn usz? available() @optional; fn usz? read(char[] buffer); fn char? read_byte(); fn usz? write_to(OutStream out) @optional; fn void? pushback_byte() @optional; } interface OutStream { fn void? destroy() @optional; fn void? close() @optional; fn void? flush() @optional; fn usz? write(char[] bytes); fn void? write_byte(char c); fn usz? read_to(InStream in) @optional; } fn usz? available(InStream s) { if (&s.available) return s.available(); if (&s.seek) { usz curr = s.seek(0, Seek.CURSOR)!; usz len = s.seek(0, Seek.END)!; s.seek(curr, Seek.SET)!; return len - curr; } return io::UNSUPPORTED_OPERATION~; } macro bool @is_instream(#expr) @const { return $defined(InStream i = #expr); } macro bool @is_not_instream_if_ptr(#expr) @const { return !$defined(&#expr) ||| !@is_instream(&#expr); } macro bool @is_outstream(#expr) @const { return $defined(OutStream s = #expr); } macro bool @is_not_outstream_if_ptr(#expr) @const { return !$defined(&#expr) ||| !@is_outstream(&#expr); } <* @param [&out] ref @require @is_instream(stream) : "Expected a stream" *> macro usz? read_any(stream, any ref) { return read_all(stream, ((char*)ref)[:ref.type.sizeof]); } <* @param [&in] ref : "the object to write." @require @is_outstream(stream) @ensure return == ref.type.sizeof *> macro usz? write_any(stream, any ref) { return write_all(stream, ((char*)ref)[:ref.type.sizeof]); } <* @require @is_instream(stream) *> macro usz? read_all(stream, char[] buffer) { if (buffer.len == 0) return 0; usz n = stream.read(buffer)!; if (n != buffer.len) return UNEXPECTED_EOF?; return n; } <* This function will read to the end of the stream. @require @is_instream(stream) *> macro char[]? read_fully(Allocator allocator, stream) { // Efficient path if it is possible to pre-allocate if (try len = available(stream)) { char* data = allocator::malloc_try(allocator, len)!; defer catch allocator::free(allocator, data); usz read = 0; while (read < len) { read += stream.read(data[read:len - read])!; } return data[:len]; } ByteWriter writer; writer.init(allocator); copy_to(stream, &writer)!; return writer.array_view(); } <* @require @is_outstream(stream) *> macro usz? write_all(stream, char[] buffer) { if (buffer.len == 0) return 0; usz n = stream.write(buffer)!; if (n != buffer.len) return INCOMPLETE_WRITE?; return n; } <* @require @is_instream(s) *> macro usz? read_using_read_byte(s, char[] buffer) { usz len = 0; foreach (&cptr : buffer) { char? c = s.read_byte(); if (catch err = c) { if (err == io::EOF) return len; return err?; } *cptr = c; len++; } return len; } <* @require @is_outstream(s) *> macro void? write_byte_using_write(s, char c) { char[1] buff = { c }; s.write(&buff)!; } <* @require @is_instream(s) *> macro char? read_byte_using_read(s) { char[1] buffer; usz read = s.read(&buffer)!; if (read != 1) return io::EOF?; return buffer[0]; } alias ReadByteFn = fn char?(); <* @require @is_outstream(s) *> macro usz? write_using_write_byte(s, char[] bytes) { foreach (c : bytes) s.write_byte(c)!; return bytes.len; } macro void? pushback_using_seek(s) { s.seek(-1, CURSOR)!; } fn usz? copy_to(InStream in, OutStream dst, char[] buffer = {}) { if (buffer.len) return copy_through_buffer(in, dst, buffer); if (&in.write_to) return in.write_to(dst); if (&dst.read_to) return dst.read_to(in); $switch env::MEMORY_ENV: $case NORMAL: return copy_through_buffer(in, dst, &&(char[4096]){}); $case SMALL: return copy_through_buffer(in, dst, &&(char[1024]){}); $case TINY: $case NONE: return copy_through_buffer(in, dst, &&(char[256]){}); $endswitch } macro usz? copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local { usz total_copied; while (true) { usz? len = in.read(buffer); if (catch err = len) { if (err == io::EOF) return total_copied; return err?; } if (!len) return total_copied; usz written = dst.write(buffer[:len])!; total_copied += len; if (written != len) return INCOMPLETE_WRITE?; } } const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 }; <* @require @is_instream(stream) @require $kindof(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int() *> macro usz? read_varint(stream, x_ptr) { var $Type = $typeof(x_ptr).inner; const MAX = MAX_VARS[$Type.sizeof]; $Type x; uint shift; usz n; for (usz i = 0; i < MAX; i++) { char? c = stream.read_byte(); if (catch err = c) { if (err == io::EOF) return io::UNEXPECTED_EOF?; return err?; } n++; if (c & 0x80 == 0) { if (i + 1 == MAX && c > 1) break; x |= c << shift; $if $Type.kindof == SIGNED_INT: x = x & 1 == 0 ? x >> 1 : ~(x >> 1); $endif *x_ptr = x; return n; } x |= (c & 0x7F) << shift; shift += 7; } return math::OVERFLOW?; } <* @require @is_outstream(stream) @require $kindof(x).is_int() *> macro usz? write_varint(stream, x) { var $Type = $typeof(x); const MAX = MAX_VARS[$Type.sizeof]; char[MAX] buffer @noinit; usz i; while (x >= 0x80) { buffer[i] = (char)(x | 0x80); x >>= 7; i++; } buffer[i] = (char)x; return write_all(stream, buffer[:i + 1]); } <* @require @is_instream(stream) *> macro ushort? read_be_ushort(stream) { char hi_byte = stream.read_byte()!; char lo_byte = stream.read_byte()!; return (ushort)(hi_byte << 8 | lo_byte); } <* @require @is_instream(stream) *> macro ushort? read_le_ushort(stream) { char lo_byte = stream.read_byte()!; char hi_byte = stream.read_byte()!; return (ushort)(hi_byte << 8 | lo_byte); } <* @require @is_instream(stream) *> macro short? read_be_short(stream) { return read_be_ushort(stream); } <* @require @is_instream(stream) *> macro short? read_le_short(stream) { return read_le_ushort(stream); } <* @require @is_outstream(stream) *> macro void? write_be_short(stream, ushort s) { stream.write_byte((char)(s >> 8))!; stream.write_byte((char)s)!; } <* @require @is_outstream(stream) *> macro void? write_le_short(stream, ushort s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; } <* @require @is_instream(stream) *> macro uint? read_be_uint(stream) { uint val = stream.read_byte()! << 24; val += stream.read_byte()! << 16; val += stream.read_byte()! << 8; return val + stream.read_byte()!; } <* @require @is_instream(stream) *> macro uint? read_le_uint(stream) { uint val = stream.read_byte()!; val += stream.read_byte()! << 8; val += stream.read_byte()! << 16; return val + stream.read_byte()! << 24; } <* @require @is_instream(stream) *> macro int? read_be_int(stream) { return read_be_uint(stream); } <* @require @is_instream(stream) *> macro int? read_le_int(stream) { return read_le_uint(stream); } <* @require @is_outstream(stream) *> macro void? write_be_int(stream, uint s) { stream.write_byte((char)(s >> 24))!; stream.write_byte((char)(s >> 16))!; stream.write_byte((char)(s >> 8))!; stream.write_byte((char)s)!; } <* @require @is_outstream(stream) *> macro void? write_le_int(stream, uint s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; stream.write_byte((char)(s >> 16))!; stream.write_byte((char)(s >> 24))!; } <* @require @is_instream(stream) *> macro ulong? read_be_ulong(stream) { ulong val = (ulong)stream.read_byte()! << 56; val += (ulong)stream.read_byte()! << 48; val += (ulong)stream.read_byte()! << 40; val += (ulong)stream.read_byte()! << 32; val += (ulong)stream.read_byte()! << 24; val += (ulong)stream.read_byte()! << 16; val += (ulong)stream.read_byte()! << 8; return val + stream.read_byte()!; } <* @require @is_instream(stream) *> macro ulong? read_le_ulong(stream) { ulong val = (ulong)stream.read_byte()!; val += (ulong)stream.read_byte()! << 8; val += (ulong)stream.read_byte()! << 16; val += (ulong)stream.read_byte()! << 24; val += (ulong)stream.read_byte()! << 32; val += (ulong)stream.read_byte()! << 40; val += (ulong)stream.read_byte()! << 48; return val + (ulong)stream.read_byte()! << 56; } <* @require @is_instream(stream) *> macro long? read_be_long(stream) { return read_be_ulong(stream); } <* @require @is_instream(stream) *> macro long? read_le_long(stream) { return read_le_ulong(stream); } <* @require @is_outstream(stream) *> macro void? write_be_long(stream, ulong s) { stream.write_byte((char)(s >> 56))!; stream.write_byte((char)(s >> 48))!; stream.write_byte((char)(s >> 40))!; stream.write_byte((char)(s >> 32))!; stream.write_byte((char)(s >> 24))!; stream.write_byte((char)(s >> 16))!; stream.write_byte((char)(s >> 8))!; stream.write_byte((char)s)!; } <* @require @is_outstream(stream) *> macro void? write_le_long(stream, ulong s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; stream.write_byte((char)(s >> 16))!; stream.write_byte((char)(s >> 24))!; stream.write_byte((char)(s >> 32))!; stream.write_byte((char)(s >> 40))!; stream.write_byte((char)(s >> 48))!; stream.write_byte((char)(s >> 56))!; } <* @require @is_instream(stream) *> macro uint128? read_be_uint128(stream) { uint128 val = (uint128)stream.read_byte()! << 120; val += (uint128)stream.read_byte()! << 112; val += (uint128)stream.read_byte()! << 104; val += (uint128)stream.read_byte()! << 96; val += (uint128)stream.read_byte()! << 88; val += (uint128)stream.read_byte()! << 80; val += (uint128)stream.read_byte()! << 72; val += (uint128)stream.read_byte()! << 64; val += (uint128)stream.read_byte()! << 56; val += (uint128)stream.read_byte()! << 48; val += (uint128)stream.read_byte()! << 40; val += (uint128)stream.read_byte()! << 32; val += (uint128)stream.read_byte()! << 24; val += (uint128)stream.read_byte()! << 16; val += (uint128)stream.read_byte()! << 8; return val + stream.read_byte()!; } <* @require @is_instream(stream) *> macro uint128? read_le_uint128(stream) { uint128 val = stream.read_byte()!; val += (uint128)stream.read_byte()! << 8; val += (uint128)stream.read_byte()! << 16; val += (uint128)stream.read_byte()! << 24; val += (uint128)stream.read_byte()! << 32; val += (uint128)stream.read_byte()! << 40; val += (uint128)stream.read_byte()! << 48; val += (uint128)stream.read_byte()! << 56; val += (uint128)stream.read_byte()! << 64; val += (uint128)stream.read_byte()! << 72; val += (uint128)stream.read_byte()! << 80; val += (uint128)stream.read_byte()! << 88; val += (uint128)stream.read_byte()! << 96; val += (uint128)stream.read_byte()! << 104; val += (uint128)stream.read_byte()! << 112; return val + (uint128)stream.read_byte()! << 120; } <* @require @is_instream(stream) *> macro int128? read_be_int128(stream) { return read_be_uint128(stream); } <* @require @is_instream(stream) *> macro int128? read_le_int128(stream) { return read_le_uint128(stream); } <* @require @is_outstream(stream) *> macro void? write_be_int128(stream, uint128 s) { stream.write_byte((char)(s >> 120))!; stream.write_byte((char)(s >> 112))!; stream.write_byte((char)(s >> 104))!; stream.write_byte((char)(s >> 96))!; stream.write_byte((char)(s >> 88))!; stream.write_byte((char)(s >> 80))!; stream.write_byte((char)(s >> 72))!; stream.write_byte((char)(s >> 64))!; stream.write_byte((char)(s >> 56))!; stream.write_byte((char)(s >> 48))!; stream.write_byte((char)(s >> 40))!; stream.write_byte((char)(s >> 32))!; stream.write_byte((char)(s >> 24))!; stream.write_byte((char)(s >> 16))!; stream.write_byte((char)(s >> 8))!; stream.write_byte((char)s)!; } <* @require @is_outstream(stream) *> macro void? write_le_int128(stream, uint128 s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; stream.write_byte((char)(s >> 16))!; stream.write_byte((char)(s >> 24))!; stream.write_byte((char)(s >> 32))!; stream.write_byte((char)(s >> 40))!; stream.write_byte((char)(s >> 48))!; stream.write_byte((char)(s >> 56))!; stream.write_byte((char)(s >> 64))!; stream.write_byte((char)(s >> 72))!; stream.write_byte((char)(s >> 80))!; stream.write_byte((char)(s >> 88))!; stream.write_byte((char)(s >> 96))!; stream.write_byte((char)(s >> 104))!; stream.write_byte((char)(s >> 112))!; stream.write_byte((char)(s >> 120))!; } <* @require @is_outstream(stream) @require data.len < 256 : "Data exceeded 255" *> macro usz? write_tiny_bytearray(stream, char[] data) { stream.write_byte((char)data.len)!; return stream.write(data) + 1; } <* @require @is_instream(stream) *> macro char[]? read_tiny_bytearray(stream, Allocator allocator) { int len = stream.read_byte()!; if (!len) return {}; char[] data = allocator::alloc_array(allocator, char, len); io::read_all(stream, data)!; return data; } <* @require @is_outstream(stream) @require data.len < 0x1000 : "Data exceeded 65535" *> macro usz? write_short_bytearray(stream, char[] data) { io::write_be_short(stream, (ushort)data.len)!; return stream.write(data) + 2; } <* @require @is_instream(stream) *> macro char[]? read_short_bytearray(stream, Allocator allocator) { int len = io::read_be_ushort(stream)!; if (!len) return {}; char[] data = allocator::alloc_array(allocator, char, len); io::read_all(stream, data)!; return data; } <* @require @is_instream(stream) *> macro void? skip(stream, usz bytes) { if (!bytes) return; $switch: $case !$defined(stream.seek): for (usz i = 0; i < bytes; i++) { stream.read()!; } return; $case $typeof(stream) == InStream: if (!&stream.seek) { for (usz i = 0; i < bytes; i++) { stream.read()!; } return; } stream.seek(bytes, CURSOR)!; $default: stream.seek(bytes, CURSOR)!; $endswitch } <* Wrap bytes for reading using io functions. *> fn ByteReader wrap_bytes(char[] bytes) { return { bytes, 0 }; }