package core.io
memory :: package core.memory
+math :: package core.math
Reader :: struct {
stream : ^Stream;
+
+ buffer: [] u8;
+ buffer_allocator: Allocator;
+
+ start, end: u32; // The start and ending positions of the edges of the buffer "window".
+
+ last_byte: i32;
+
+ error: Error;
+
+ done : bool; // If an .EOF was reached.
}
-reader_make :: (s: ^Stream) -> Reader {
+reader_make :: (s: ^Stream, buffer_size := 4096, allocator := context.allocator) -> Reader {
assert(s.vtable != null, "Stream vtable was not setup correctly.");
-
- return Reader.{ s };
+
+ reader: Reader;
+ reader.stream = s;
+
+ memory.alloc_slice(^reader.buffer, buffer_size, allocator);
+ reader.buffer_allocator = allocator;
+
+ reader_reset(^reader);
+
+ return reader;
+}
+
+reader_reset :: (use reader: ^Reader) {
+ start, end = 0, 0;
+ last_byte = #char "\0";
+ done = false;
+}
+
+reader_free :: (use reader: ^Reader) {
+ memory.free_slice(^buffer, buffer_allocator);
}
// You need to free the StringStream from the context.allocator when you use this. For example,
stream_ptr := new(StringStream);
*stream_ptr = string_stream_make(s);
- return Reader.{ stream_ptr }, stream_ptr;
+ return reader_make(stream_ptr), stream_ptr;
+}
+
+reader_get_buffered :: (use reader: ^Reader) -> i32 {
+ return end - start;
}
reader_empty :: (use reader: ^Reader) -> bool {
- return stream_end_of_file(stream);
+ return done && reader_get_buffered(reader) == 0;
}
read_byte :: (use reader: ^Reader) -> u8 {
- err, byte := stream_read_byte(stream);
+ while start == end {
+ if error != .None {
+ reader_consume_error(reader);
+ return 0;
+ }
+
+ if err := reader_read_next_chunk(reader); err != .None {
+ return 0;
+ }
+ }
+
+ byte := buffer[start];
+ start += 1;
+ last_byte = cast(i32) byte;
return byte;
}
-read_bytes :: #match {
- (use reader: ^Reader, bytes := 1, allocator := context.allocator) -> str {
- buffer := memory.make_slice(u8, bytes, allocator);
- stream_read(stream, buffer);
- return buffer;
- },
+unread_byte :: (use reader: ^Reader) -> Error {
+ if last_byte < 0 || (start == 0 && end > 0) {
+ return .InvalidUnread;
+ }
- (use reader: ^Reader, bytes: [] u8) -> str {
- err, bytes_read := stream_read(stream, bytes);
- return bytes;
+ if start > 0 {
+ start -= 1;
+ } else {
+ end = 1;
}
+
+ buffer[start] = cast(u8) last_byte;
+ last_byte = -1;
+ return .None;
}
+read_bytes :: (use reader: ^Reader, bytes: [] u8) -> (i32, Error) {
+ n := bytes.count;
+ if n == 0 {
+ if reader_get_buffered(reader) > 0 do return 0, .None;
+ return 0, reader_consume_error(reader);
+ }
+
+ if start == end {
+ if error != .None do return 0, reader_consume_error(reader);
+
+ if n >= buffer.count {
+ error, n = stream_read(stream, bytes);
+
+ if n > 0 do last_byte = cast(i32) bytes[n - 1];
+
+ return n, reader_consume_error(reader);
+ }
+
+ start, end = 0, 0;
+ error, n = stream_read(stream, buffer);
+ if n == 0 do return 0, reader_consume_error(reader);
+ end += n;
+ }
+
+ memory.copy(bytes.data, buffer.data + start, n);
+ start += n;
+ last_byte = cast(i32) buffer[start - 1];
+ return n, .None;
+}
+
+read_string :: (use reader: ^Reader, bytes := 1, allocator := context.allocator) -> str {
+ buf := memory.make_slice(u8, bytes, allocator);
+ bytes_read, err := read_bytes(reader, buf);
+ return buf[0 .. bytes_read];
+};
+
read_i32 :: (use reader: ^Reader) -> i32 {
n: i32 = 0;
skip_whitespace(reader);
is_negative := false;
- err, curr := stream_peek_byte(stream);
+ curr, err := peek_byte(reader);
if curr == #char "-" {
is_negative = true;
- err, curr = stream_read_byte(stream);
- err, curr = stream_peek_byte(stream);
+ start += 1;
}
+ curr, err = peek_byte(reader);
while curr >= #char "0" && curr <= #char "9" {
- err, curr = stream_read_byte(stream);
-
n *= 10;
n += cast(u32) (curr - #char "0");
- err, curr = stream_peek_byte(stream);
- if err == Error.EOF do break;
+ start += 1;
+ curr, err = peek_byte(reader);
+ if err != .None do break;
}
if is_negative do n = -n;
skip_whitespace(reader);
is_negative := false;
- err, curr := stream_peek_byte(stream);
+ curr, err := peek_byte(reader);
if curr == #char "-" {
is_negative = true;
- err, curr = stream_read_byte(stream);
- err, curr = stream_peek_byte(stream);
+ start += 1;
}
+ curr, err = peek_byte(reader);
while curr >= #char "0" && curr <= #char "9" {
- err, curr = stream_read_byte(stream);
-
n *= 10;
n += cast(u64) (curr - #char "0");
- err, curr = stream_peek_byte(stream);
- if err == Error.EOF do break;
+ start += 1;
+ curr, err = peek_byte(reader);
+ if err != .None do break;
}
if is_negative do n = -n;
skip_whitespace(reader);
- err, curr := stream_peek_byte(stream);
+ curr, err := peek_byte(reader);
while curr >= #char "0" && curr <= #char "9" {
- err, curr = stream_read_byte(stream);
-
n *= 10;
n += cast(u32) (curr - #char "0");
- err, curr = stream_peek_byte(stream);
- if err == Error.EOF do break;
+ start += 1;
+ curr, err = peek_byte(reader);
+ if err != .None do break;
}
return n;
skip_whitespace(reader);
- err, curr := stream_peek_byte(stream);
+ curr, err := peek_byte(reader);
while curr >= #char "0" && curr <= #char "9" {
- err, curr = stream_read_byte(stream);
-
n *= 10;
n += cast(u64) (curr - #char "0");
- err, curr = stream_peek_byte(stream);
- if err == Error.EOF do break;
+ start += 1;
+ curr, err = peek_byte(reader);
+ if err != .None do break;
}
return n;
}
read_line :: (use reader: ^Reader, consume_newline := true, allocator := context.allocator) -> str {
- _, curr_pos := stream_tell(stream);
+ reader_read_next_chunk(reader);
- count := 0;
- err, curr := stream_read_byte(stream);
- while curr != #char "\n" {
+ count := start;
+ while count < end && buffer[count] != #char "\n" {
count += 1;
-
- err, curr = stream_read_byte(stream);
- if err != Error.None do break;
}
- if consume_newline do count += 1;
-
- stream_seek(stream, curr_pos, SeekFrom.Start);
+ if consume_newline && count < end {
+ count += 1;
+ }
out := str.{
data = raw_alloc(allocator, count * sizeof(u8)),
- count = count,
+ count = count - start,
};
- stream_read(stream, out);
+ memory.copy(out.data, buffer.data + start, count - start);
+ start = count;
return out;
}
read_word :: (use reader: ^Reader, numeric_allowed := false, allocator := context.allocator) -> str {
skip_whitespace(reader);
+ reader_read_next_chunk(reader);
- _, curr_pos := stream_tell(stream);
-
- count := 0;
- err, curr := stream_read_byte(stream);
-
- while true {
+ count := start;
+ while count < end {
+ curr := buffer[count];
if (curr >= #char "a" && curr <= #char "z")
|| (curr >= #char "A" && curr <= #char "Z")
|| curr == #char "_" {
} else {
break;
}
-
- err, curr = stream_read_byte(stream);
- if err != Error.None do break;
}
- stream_seek(stream, curr_pos, SeekFrom.Start);
-
out := str.{
data = raw_alloc(allocator, count * sizeof(u8)),
- count = count,
+ count = count - start,
};
- stream_read(stream, out);
+ memory.copy(out.data, buffer.data + start, count - start);
+ start = count;
return out;
}
read_until :: (use reader: ^Reader, until: u8, skip: u32 = 0, allocator := context.allocator, consume_end := false) -> str {
- _, curr_pos := stream_tell(stream);
-
- count := 0;
- err, curr := stream_read_byte(stream);
+ reader_read_next_chunk(reader);
- while true {
+ count := start;
+ while count < end {
+ curr := buffer[count];
if curr != until {
count += 1;
} else {
if skip == 0 do break;
else do skip -= 1;
}
-
- err, curr = stream_read_byte(stream);
- if err != Error.None do break;
}
- if consume_end do count += 1;
-
- stream_seek(stream, curr_pos, SeekFrom.Start);
+ if consume_end && count < end do count += 1;
out := str.{
data = raw_alloc(allocator, count * sizeof(u8)),
- count = count,
+ count = count - start,
};
- stream_read(stream, out);
+ memory.copy(out.data, buffer.data + start, count - start);
+ start = count;
return out;
}
-peek_byte :: (use reader: ^Reader) -> u8 {
- err, byte := stream_peek_byte(stream);
- return byte;
+peek_byte :: (use reader: ^Reader) -> (u8, Error) {
+ if start == end {
+ if fill_err := reader_read_next_chunk(reader); fill_err != .None {
+ return 0, fill_err;
+ }
+ }
+
+ return buffer[start], .None;
}
advance_line :: (use reader: ^Reader) {
- err, curr := stream_read_byte(stream);
- if err != Error.None do return;
-
- while curr != #char "\n" {
- err, curr = stream_read_byte(stream);
- if err != Error.None do return;
- }
+ while true {
+ if start == end {
+ if fill_err := reader_read_next_chunk(reader); fill_err != .None {
+ return;
+ }
+ }
- err, curr = stream_read_byte(stream);
+ defer start += 1;
+ if buffer[start] == #char "\n" {
+ return;
+ }
+ }
}
skip_whitespace :: (use reader: ^Reader) {
- while true {
- err, byte := stream_peek_byte(stream);
- if err == Error.EOF do break;
+ while start < end {
+ if start == end {
+ if fill_err := reader_read_next_chunk(reader); fill_err != .None {
+ return;
+ }
+ }
- switch byte {
+ switch buffer[start] {
case #char " ", #char "\t", #char "\n", #char "\r" {
- err, byte = stream_read_byte(stream);
+ start += 1;
}
case #default do return;
}
}
-skip_bytes :: (use reader: ^Reader, bytes: u32) {
- for _: bytes do stream_read_byte(stream);
+skip_bytes :: (use reader: ^Reader, bytes: u32) -> (skipped: i32, err: Error) {
+ if bytes == 0 do return 0, .None;
+
+ remaining := bytes;
+ while true {
+ skip := reader_get_buffered(reader);
+ if skip == 0 {
+ if fill_err := reader_read_next_chunk(reader); fill_err != .None {
+ return 0, fill_err;
+ }
+
+ skip = reader_get_buffered(reader);
+ }
+
+ skip = math.min(skip, remaining);
+ start += skip;
+ remaining -= skip;
+
+ if remaining == 0 do return bytes, .None;
+
+ if error != .None {
+ return bytes - remaining, reader_consume_error(reader);
+ }
+ }
+
+ return 0, .None;
+}
+
+#local reader_consume_error :: (use reader: ^Reader) -> Error {
+ defer error = .None;
+ return error;
}
+
+#local reader_read_next_chunk :: (use reader: ^Reader) -> Error {
+ if done do return .None;
+
+ if start > 0 {
+ // This assumes that memory.copy behaves like memmove, in that the
+ // buffer may overlap, but it should do the right thing.
+ memory.copy(buffer.data, buffer.data + start, end - start);
+ end -= start;
+ start = 0;
+ }
+
+ if end >= buffer.count {
+ return .BufferFull;
+ }
+
+ // Try to re-read multiple times
+ for 4 {
+ err, n := stream_read(stream, buffer[end .. buffer.count]);
+ end += n;
+ if err != .None {
+ if err == .EOF do done = true;
+
+ error = err;
+ return .None;
+ }
+
+ if n > 0 {
+ return .None;
+ }
+ }
+
+ done = true;
+ error = .NoProgress;
+ return .None;
+}
\ No newline at end of file
// #package
Stream_Vtable :: struct {
- seek : (s: ^Stream, to: i32, whence: SeekFrom) -> Error;
- tell : (s: ^Stream) -> (Error, u32);
+ seek : (s: ^Stream, to: i32, whence: SeekFrom) -> Error = null_proc;
+ tell : (s: ^Stream) -> (Error, u32) = null_proc;
- read : (s: ^Stream, buffer: [] u8) -> (Error, u32);
- read_at : (s: ^Stream, at: u32, buffer: [] u8) -> (Error, u32);
- read_byte : (s: ^Stream) -> (Error, u8);
- unread_byte : (s: ^Stream) -> Error;
+ read : (s: ^Stream, buffer: [] u8) -> (Error, u32) = null_proc;
+ read_at : (s: ^Stream, at: u32, buffer: [] u8) -> (Error, u32) = null_proc;
+ read_byte : (s: ^Stream) -> (Error, u8) = null_proc;
- write : (s: ^Stream, buffer: [] u8) -> (Error, u32);
- write_at : (s: ^Stream, at: u32, buffer: [] u8) -> (Error, u32);
- write_byte : (s: ^Stream, byte: u8) -> Error;
+ write : (s: ^Stream, buffer: [] u8) -> (Error, u32) = null_proc;
+ write_at : (s: ^Stream, at: u32, buffer: [] u8) -> (Error, u32) = null_proc;
+ write_byte : (s: ^Stream, byte: u8) -> Error = null_proc;
- close : (s: ^Stream) -> Error;
- flush : (s: ^Stream) -> Error;
+ close : (s: ^Stream) -> Error = null_proc;
+ flush : (s: ^Stream) -> Error = null_proc;
- size : (s: ^Stream) -> i32;
+ size : (s: ^Stream) -> i32 = null_proc;
}
SeekFrom :: enum {
return vtable.read_byte(s);
}
-stream_unread_byte :: (use s: ^Stream) -> Error {
- if vtable == null do return .NoVtable;
- if vtable.unread_byte == null_proc do return .NotImplemented;
-
- return vtable.unread_byte(s);
-}
-
stream_write :: (use s: ^Stream, buffer: [] u8) -> (Error, u32) {
if vtable == null do return .NoVtable, 0;
if vtable.write == null_proc do return .NotImplemented, 0;
return vtable.size(s);
}
-stream_peek_byte :: (use s: ^Stream) -> (Error, u8) {
- err, out := stream_read_byte(s);
- if err != .None do return err, 0;
-
- err = stream_unread_byte(s);
- if err != .None do return err, 0;
-
- return .None, out;
-}
-
-stream_end_of_file :: (use s: ^Stream) -> bool {
- err, _ := stream_peek_byte(s);
- return err == .EOF;
-}
-
-
//
// StringStream
return .None, data[curr_pos];
},
- unread_byte = (use ss: ^StringStream) -> Error {
- if curr_pos <= 0 do return .OutOfBounds;
-
- curr_pos -= 1;
- return .None;
- },
-
write = (use ss: ^StringStream, buffer: [] u8) -> (Error, u32) {
if curr_pos >= data.count do return .EOF, 0;
size = (use ss: ^StringStream) -> i32 {
return data.count;
- },
-
- close = null_proc,
- flush = null_proc,
+ }
}
return .None, data[curr_pos];
},
- unread_byte = (use dss: ^DynamicStringStream) -> Error {
- if curr_pos <= 0 do return .OutOfBounds;
-
- curr_pos -= 1;
- return .None;
- },
-
write = (use dss: ^DynamicStringStream, buffer: [] u8) -> (Error, u32) {
if curr_pos + buffer.count >= data.capacity {
if !array.ensure_capacity(^data, curr_pos + buffer.count) do return .EOF, 0;
return .None;
},
-
- close = null_proc,
}