Last active
December 2, 2019 16:25
-
-
Save thebirk/48bd37959a3d1132d5708577ef1c7947 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io | |
import "core:os" | |
import "core:mem" | |
import "core:log" | |
import "core:runtime" | |
Error :: enum { | |
Ok, | |
Could_Not_Create_File, | |
Could_Not_Open_File, | |
Could_Not_Read_From_File, | |
Not_Enough_Bytes, | |
End_Of_Stream, | |
} | |
/* | |
Another possible base structure could be: | |
Writer :: struct { | |
write_proc: proc(writer: ^Writer, data: []byte) -> (int, Error), | |
user_data: any, | |
} | |
*/ | |
Writer :: struct { | |
// Could also include some more specific actions, like `close` and `flush` | |
write_proc: proc(writer: ^Writer, data: []byte) -> (int, Error), | |
} | |
write :: inline proc(using writer: ^Writer, data: []byte) -> (int, Error) { | |
return writer.write_proc(writer, data); | |
} | |
write_string :: inline proc(using writer: ^Writer, s: string) -> (int, Error) { | |
return writer.write_proc(writer, cast([]u8) s); | |
} | |
write_byte :: inline proc(using writer: ^Writer, b: byte) -> (int, Error) { | |
return writer.write_proc(writer, []u8{b}); | |
} | |
write_any :: proc(using writer: ^Writer, data: any) -> (int, Error) { | |
// use type information to write structs, enums, unions, etc. | |
// have corresponding read_any. | |
tb := runtime.type_info_base(type_info_of(data.id)); | |
switch ti in tb.variant { | |
case runtime.Type_Info_Struct: | |
bytes := mem.any_to_bytes(data); | |
return write(writer, bytes); | |
case runtime.Type_Info_String: | |
return write_string(writer, data.(string)); | |
case: | |
fmt.panicf("write_any: type '%v' not supported", tb.id); | |
return 0, .Ok; | |
} | |
} | |
Reader :: struct { | |
read_proc: proc(reader: ^Reader, data: []byte) -> (int, Error), | |
} | |
read :: inline proc(using reader: ^Reader, data: []byte) -> (int, Error) { | |
return reader.read_proc(reader, data); | |
} | |
read_byte :: inline proc(using reader: ^Reader) -> (byte, Error) { | |
buffer: [1]u8; | |
n, err := reader.read_proc(reader, buffer[:]); | |
if err != .Ok { | |
return 0, err; | |
} | |
if n == 0 { | |
// What do we do here? Another error code? EOS error code? | |
return 0, .Ok; | |
} | |
return buffer[0], .Ok; | |
} | |
read_u32le :: inline proc(using reader: ^Reader) -> (u32le, Error) { | |
return read_typeid(reader, u32le); | |
} | |
read_typeid :: inline proc(using reader: ^Reader, $T: typeid) -> (T, Error) { | |
buffer: [size_of(T)]byte; | |
l, err := read(reader, buffer[:]); | |
if l != size_of(T) { | |
return {}, .Not_Enough_Bytes; | |
} | |
return transmute(T) buffer, .Ok; | |
} | |
nil_write_proc :: proc(writer: ^Writer, data: []byte) -> (int, Error) { | |
return len(data), .Ok; | |
} | |
@private | |
_nil_writer := Writer{nil_write_proc}; | |
nil_writer :: proc() -> ^Writer { | |
return &_nil_writer; | |
} | |
////////////////////////////// | |
/// File Writer and Reader /// | |
////////////////////////////// | |
File_Writer :: struct { | |
using writer: Writer, | |
handle: os.Handle, | |
} | |
file_write_proc :: proc(using w: ^File_Writer, data: []byte) -> (int, Error) { | |
os.write(handle, data); | |
return len(data), .Ok; | |
} | |
create_file :: proc(filename: string) -> (File_Writer, Error) { | |
fw := File_Writer{}; | |
fw.write_proc = cast(type_of(fw.write_proc)) file_write_proc; | |
handle, err := os.open(filename, os.O_CREATE|os.O_TRUNC); | |
if err != os.ERROR_NONE { | |
log.error("create_file failed to open file, errno: %v", err); | |
return {}, .Could_Not_Create_File; | |
} | |
fw.handle = handle; | |
return fw, .Ok; | |
} | |
open_file_writer :: proc(filename: string) -> (File_Writer, Error) { | |
fw := File_Writer{}; | |
fw.write_proc = cast(type_of(fw.write_proc)) file_write_proc; | |
handle, err := os.open(filename, os.O_APPEND); | |
if err != os.ERROR_NONE { | |
log.error("open_file_writer failed to open file, errno: %v", err); | |
return {}, .Could_Not_Open_File; | |
} | |
fw.handle = handle; | |
return fw, .Ok; | |
} | |
close_file_writer :: proc(fw: ^File_Writer) { | |
os.close(fw.handle); | |
} | |
File_Reader :: struct { | |
using reader: Reader, | |
handle: os.Handle, | |
} | |
file_read_proc :: proc(using r: ^File_Reader, data: []byte) -> (int, Error) { | |
l, err := os.read(r.handle, data); | |
if err != os.ERROR_NONE { | |
return {}, .Could_Not_Read_From_File; | |
} | |
if l == 0 { | |
return 0, .End_Of_Stream; | |
} | |
return l, .Ok; | |
} | |
open_file_reader :: proc(filename: string) -> (File_Reader, Error) { | |
fr := File_Reader{}; | |
fr.read_proc = cast(type_of(fr.read_proc)) file_read_proc; | |
handle, err := os.open(filename, os.O_RDONLY); | |
if err != os.ERROR_NONE { | |
log.error("open_file_reader failed to open file, errno: %v", err); | |
return {}, .Could_Not_Open_File; | |
} | |
fr.handle = handle; | |
return fr, .Ok; | |
} | |
close_file_reader :: proc(fr: ^File_Reader) { | |
os.close(fr.handle); | |
} | |
//////////////////// | |
/// Multi Writer /// | |
//////////////////// | |
// Implement a seperate error code for Multi_Writer that indicates that some error occured | |
// and then store that error next to the writer in a structur perhaps a little like this: | |
// | |
// writers: [dynamic]struct{^Writer, Error} | |
// | |
Multi_Writer :: struct { | |
using writer: Writer, | |
writers: [dynamic]^Writer, | |
} | |
multi_write_proc :: proc(using w: ^Multi_Writer, data: []byte) -> (int, Error) { | |
for w in writers { | |
l, err := w.write_proc(w, data); | |
if err != .Ok { | |
return l, err; | |
} | |
} | |
return len(data), .Ok; | |
} | |
make_multi_writer :: proc(writers: []^Writer) -> Multi_Writer { | |
mw := Multi_Writer{}; | |
mw.write_proc = cast(type_of(mw.write_proc)) multi_write_proc; | |
for w in writers { | |
append(&mw.writers, w); | |
} | |
return mw; | |
} | |
delete_multi_writer :: proc(mw: ^Multi_Writer) { | |
delete(mw.writers); | |
} | |
//////////////////////////////// | |
/// Socket Reader and Writer /// | |
//////////////////////////////// | |
when false { | |
Socket_Writer :: struct { | |
using writer: Writer, | |
socket_handle: sock.Socket, | |
} | |
socket_write_proc :: proc(using w: ^Socket_Writer, data: []byte) -> (int, Error) { | |
n := sock.write(socket_handle, data, len(data)); | |
if n < 0 { | |
// Handle errno | |
return 0, .Socked_Write_Error; | |
} | |
return n, .Ok; | |
} | |
make_socket_writer :: proc(sock: sock.Socket) -> Socket_Writer { | |
sw := Socket_Writer{}; | |
sw.write_proc = cast(type_of(sw.write_proc)) socket_write_proc; | |
sw.socket_handle = sock; | |
return sw; | |
} | |
Socket_Reader :: struct { | |
using writer: Reader, | |
socket_handle: sock.Socket, | |
} | |
socket_read_proc :: proc(using w: ^Socket_Reader, data: []byte) -> (int, Error) { | |
n := sock.read(socket_handle, data, len(data)); | |
if n < 0 { | |
// Handle errno | |
return 0, .Socked_Read_Error; | |
} | |
return n, .Ok; | |
} | |
make_socket_reader :: proc(sock: sock.Socket) -> Socket_Reader { | |
sw := Socket_Reader{}; | |
sw.read_proc = cast(type_of(sw.read_proc)) socket_read_proc; | |
sw.socket_handle = sock; | |
return sw; | |
} | |
} | |
/////// | |
import "core:fmt" | |
Lower_Case_Writer :: struct { | |
using writer: Writer, | |
input_writer: ^Writer, | |
} | |
lower_case_write_proc :: proc(using w: ^Lower_Case_Writer, data: []byte) -> (int, Error) { | |
wrote := 0; | |
for b in data { | |
b := b; | |
if b >= 0x41 && b <= 0x5A { | |
b += 0x20; | |
} | |
n, err := write_byte(w.input_writer, b); | |
wrote += n; | |
if err != .Ok { | |
return wrote, err; | |
} | |
} | |
return wrote, .Ok; | |
}; | |
make_lower_case_writer :: proc(input_writer: ^Writer) -> Lower_Case_Writer { | |
lcw := Lower_Case_Writer{}; | |
lcw.write_proc = cast(type_of(lcw.write_proc)) lower_case_write_proc; | |
lcw.input_writer = input_writer; | |
return lcw; | |
} | |
main :: proc() { | |
context.logger = log.create_console_logger(); | |
fw, _ := create_file("test.txt"); | |
defer close_file_writer(&fw); | |
// Example of a wrapping writer | |
lcw := make_lower_case_writer(&fw); | |
log_writer := Writer { | |
proc(writer: ^Writer, data: []byte) -> (int, Error) { | |
fmt.printf("wrote %d bytes\n", len(data)); | |
return len(data), .Ok; | |
} | |
}; | |
w := make_multi_writer([]^Writer{&lcw, &log_writer, nil_writer()}); | |
defer delete_multi_writer(&w); | |
write(&w, cast([]byte) "This is some data"); | |
write_byte(&w, '\n'); | |
write_string(&w, "This is some more data\n"); | |
write_any(&w, "This is a string\n"); | |
write_any(&w, struct #packed {a: u8, b: u16, c: u32}{0x10, 0x2000, 0x40000000}); | |
{ // Reading a file 8 bytes at a time, using File_Reader | |
fr, _ := open_file_reader("test.txt"); | |
defer close_file_reader(&fr); | |
data: [8]byte; | |
for { | |
n, err := read(&fr, data[:]); | |
if err != .Ok { | |
if err == .End_Of_Stream { | |
break; | |
} else { | |
fmt.panicf("read err: %v\n", err); | |
} | |
} | |
for i in 0..<len(data) { | |
if i < n { | |
c := data[i]; | |
switch c { | |
case '\n': | |
c = '$'; | |
case ' ': | |
c = '.'; | |
} | |
fmt.printf("%c", c); | |
} else { | |
fmt.printf("0"); | |
} | |
} | |
fmt.println(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment