StringStream and DynamicStringStream -> BufferStream
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 24 Aug 2022 19:58:16 +0000 (14:58 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 24 Aug 2022 19:58:16 +0000 (14:58 -0500)
13 files changed:
core/builtin.onyx
core/container/map.onyx
core/conv.onyx
core/io/reader.onyx
core/io/stream.onyx
core/io/writer.onyx
core/stdio.onyx
src/clone.c
src/symres.c
src/types.c
tests/aoc-2020/day22.onyx
tests/aoc-2020/day24.onyx
tests/string_stream_test.onyx

index f13edc4b4905ba0313bb420d8c5d16297997fa23..0b6fe9c20c6e03359334be6230f7176c6385c238 100644 (file)
@@ -218,7 +218,7 @@ cfree   :: (ptr: rawptr) do raw_free(context.allocator, ptr);
 
     delete :: #match {
         #precedence 1000 macro (x: ^$T, allocator := context.allocator) {
-            raw_free(allocator, x);
+            if x != null do raw_free(allocator, x);
         }
     }
 }
index 72537f43640567b9ff058c645765bfe0449e9b26..df66e8c161121c807ce0a7171848beac5fe7a313 100644 (file)
@@ -76,8 +76,8 @@ init :: (use map: ^Map($K, $V), default := V.{}) {
 }
 
 free :: (use map: ^Map) {
-    if map.hashes.data != null do memory.free_slice(^hashes, allocator=allocator);
-    if map.entries.data != null do array.free(^entries);
+    if hashes.data != null  do memory.free_slice(^hashes, allocator=allocator);
+    if entries.data != null do array.free(^entries);
 }
 
 #match (package builtin).delete (package core.map).free
index 2b5ab564690b46e573a36fd9022210972f3224c7..f9642cd11d10ef0f75d44a268fc1fb046c2b6b6d 100644 (file)
@@ -577,11 +577,11 @@ format_any :: (output: ^Format_Output, formatting: ^Format, v: any) {
             // This is a little gross but the only way to output the type name for a type_expr
             // is through a io.Writer. That should maybe be changed in the future? Also, I think
             // 256 bytes is enough for the name of a type but I'm not entirely sure...
-            stream := io.string_stream_make(~~buf);
+            stream := io.buffer_stream_make(~~buf, fixed=true);
             writer := io.writer_make(^stream);
             write_type_name(^writer, value);
 
-            output->write(io.string_stream_to_str(^stream));
+            output->write(io.buffer_stream_to_str(^stream));
         }
 
         case #default {
index 56b406900eaac2c8d3fcff7e8f4d82ae0e5b0923..ab7fa0d1d964cf105baa05f37f5952199f430053 100644 (file)
@@ -67,9 +67,9 @@ reader_free :: (use reader: ^Reader) {
 // You need to free the StringStream from the context.allocator when you use this. For example,
 //      reader, stream := reader_from_string(s);
 //      defer cfree(stream);
-reader_from_string :: (s: str) -> (Reader, ^StringStream) {
-    stream_ptr := new(StringStream);
-    *stream_ptr = string_stream_make(s);
+reader_from_string :: (s: str) -> (Reader, ^BufferStream) {
+    stream_ptr := new(BufferStream);
+    *stream_ptr = buffer_stream_make(s, fixed=true, write_enabled=false);
 
     return reader_make(stream_ptr), stream_ptr;
 }
index f88ab13e61a9a3c0bdcba53861813e6fb1c147c4..bff7bfbe15aeed5c967eda2a8ed5030ce9786e97 100644 (file)
@@ -110,164 +110,126 @@ stream_size :: (use s: ^Stream) -> i32 {
 
 
 //
-// StringStream
+// BufferStream
 //
-StringStream :: struct {
-    use stream : Stream;
+// In older times of the standard library, there were two string
+// streams that existed for different purposes: StringStream and
+// DynamicStringStream. I have since merged them into one stream
+// called, BufferStream. Besides the names being confusing and
+// long, I thought it was needlessly complicated to have two
+// kinds of streams for what feels like the same task.
+//
+// When creating a BufferStream, there are two options:
+//      - You can pass a single integer, will which represent
+//          the initial size of the buffer. An initial buffer
+//          will be allocated using the allocator provided.
+//          This buffer will be write enabled, and not fixed,
+//          so it can grow to as (theoretically) large as
+//          necessary.
+//
+//      - You can pass an existing slice. The behavior of this
+//          buffer depend on the other arguments. If you set
+//          write_enabled to false, then writing will be allowed
+//          into this buffer. This is good practice if you only
+//          plan on using the stream as a read-only output for
+//          io.Reader.  If you enable writing, then you can
+//          decided if the buffer is fixed or not. If the buffer
+//          is fixed, the provided buffer will be used without
+//          allocating anything, and the data written will go
+//          directly into the provided slice. Obviously, when
+//          fixed is true, the buffer cannot grow and might result
+//          in filling the buffer and losing information. An .EOF
+//          error is returned in that case. If you pass fixed as
+//          false, the provided buffer will be copied into a new
+//          dynamic array. Note, the cursor still starts at index
+//          0, so you have to use stream_tell to move the cursor
+//          to the correct spot (This behavior might change in the
+//          future). 
+//                 
+// Remember to free the buffer using buffer_stream_free or delete.
+//
+// Note, if write_enabled is false, buffer_stream_to_str will return
+// the null string, as convert a non-writable buffer to a string does
+// not have a great natural definition.
 
-    curr_pos : i32;
-    data : str;
-}
 
-string_stream_make :: (s: str) -> StringStream {
-    return StringStream.{
-        stream = Stream.{
-            vtable   = ^string_stream_vtable,
-        },
-        data     = s,
-        curr_pos = 0,
-    };
-}
+BufferStream :: struct {
+    use stream : Stream;
 
-string_stream_to_str :: (use ss: ^StringStream) -> str {
-    return .{ data.data, curr_pos };
-}
+    data: [..] u8;
+    curr_pos := 0;
 
-#package
-string_stream_vtable := Stream_Vtable.{
-    seek = (use ss: ^StringStream, to: i32, whence: SeekFrom) -> Error {
-        if to >= data.count do return .OutOfBounds;
+    write_enabled := true;
+    fixed         := false;
+}
 
-        switch whence {
-            case SeekFrom.Start   do curr_pos = to;
-            case SeekFrom.Current do curr_pos += to;
-            case SeekFrom.End     do curr_pos = data.count - to; // CHECK: Off by one?
+buffer_stream_make :: #match #locked {
+    (initial_data: [] u8, allocator := context.allocator, fixed := false, write_enabled := true) -> BufferStream {
+        if !write_enabled {
+            return .{
+                .{ vtable = ^buffer_stream_vtable },
+                data = .{
+                    data = initial_data.data,
+                    count = initial_data.count
+                    // This does not specify the allocator,
+                    // but since write_enabled is false,
+                    // this should never be resized.
+                },
+                write_enabled = false,
+                fixed         = true
+            };
         }
 
-        return .None;
-    },
-
-    tell = (use ss: ^StringStream) -> (Error, u32) {
-        return .None, curr_pos;
-    },
-
-    read = (use ss: ^StringStream, buffer: [] u8) -> (Error, u32) {
-        if curr_pos >= data.count do return .EOF, 0;
-
-        bytes_to_read := math.min(buffer.count, data.count - curr_pos);
-        memory.copy(buffer.data, ^data.data[curr_pos], bytes_to_read);
-        curr_pos += bytes_to_read;
-
-        return .None, bytes_to_read;
-    },
-
-    read_at = (use ss: ^StringStream, at: u32, buffer: [] u8) -> (Error, u32) {
-        if at >= data.count do return .EOF, 0;
-
-        bytes_to_read := math.min(buffer.count, data.count - at);
-        memory.copy(buffer.data, ^data.data[at], bytes_to_read);
-
-        return .None, bytes_to_read;
-    },
-
-    read_byte = (use ss: ^StringStream) -> (Error, u8) {
-        if curr_pos >= data.count do return .EOF, 0;
-
-        defer curr_pos += 1;
-        return .None, data[curr_pos];
-    },
-
-    write = (use ss: ^StringStream, buffer: [] u8) -> (Error, u32) {
-        if curr_pos >= data.count do return .EOF, 0;
-
-        bytes_to_write := math.min(buffer.count, data.count - curr_pos);
-        memory.copy(^data.data[curr_pos], buffer.data, bytes_to_write);
-        curr_pos += bytes_to_write;
-
-        return .None, bytes_to_write;
-    },
-
-    write_at = (use ss: ^StringStream, at: u32, buffer: [] u8) -> (Error, u32) {
-        if at >= data.count do return .EOF, 0;
-
-        bytes_to_write := math.min(buffer.count, data.count - at);
-        memory.copy(^data.data[at], buffer.data, bytes_to_write);
-
-        return .None, bytes_to_write;
-    },
-
-    write_byte = (use ss: ^StringStream, byte: u8) -> Error {
-        if curr_pos >= data.count do return .EOF;
-
-        data[curr_pos] = byte;
-        curr_pos += 1;
+        // Make a copy of the data using the provided allocator.
+        data: [..] u8 = .{ initial_data.data, initial_data.count, initial_data.count };
+        if !fixed {
+            data = array.make(initial_data, allocator);
+        }
 
-        return .None;
+        return .{ .{ vtable = ^buffer_stream_vtable }, data, fixed=fixed };
     },
 
-    size = (use ss: ^StringStream) -> i32 {
-        return data.count;
+    (initial_size := 1024, allocator := context.allocator) -> BufferStream {
+        return .{
+            .{ vtable = ^buffer_stream_vtable },
+            make([..] u8, 1024, allocator = allocator)
+        };
     }
 }
 
-
-//
-// DynamicStringStream
-//
-DynamicStringStream :: struct {
-    use stream : Stream;
-
-    curr_pos : i32;
-    data  : [..] u8;
-
-    to_str :: dynamic_string_stream_to_str;
+buffer_stream_free :: (use bs: ^BufferStream) {
+    if write_enabled && !fixed {
+        delete(^data);
+    }
 }
 
-dynamic_string_stream_make :: (init_size := 128, a := context.allocator) -> DynamicStringStream {
-    data : [..] u8;
-    array.init(^data, init_size, allocator=a);
-
-    return DynamicStringStream.{
-        stream = Stream.{
-            vtable = ^dynamic_string_stream_vtable,
-        },
-
-        curr_pos = 0,
-        data = data,
-    };
-}
+#match builtin.delete buffer_stream_free
 
-dynamic_string_stream_free :: (use dds: ^DynamicStringStream) {
-    array.free(^data);
-}
+buffer_stream_to_str :: (use bs: ^BufferStream) -> str {
+    if !write_enabled do return null_str;
 
-dynamic_string_stream_to_str :: (use dds: ^DynamicStringStream) -> str {
-    return data.data[0 .. curr_pos];
+    return data[0 .. curr_pos];
 }
 
 #package
-dynamic_string_stream_vtable := Stream_Vtable.{
-    seek = (use dss: ^DynamicStringStream, to: i32, whence: SeekFrom) -> Error {
-        dest : i32;
-        switch whence {
-            case SeekFrom.Start   do dest = to;
-            case SeekFrom.Current do dest = curr_pos + to;
-            case SeekFrom.End     do dest = data.count - to; // CHECK: Off by one?
-        }
+buffer_stream_vtable := Stream_Vtable.{
+    seek = (use ss: ^BufferStream, to: i32, whence: SeekFrom) -> Error {
+        if to >= data.count do return .OutOfBounds;
 
-        if dest >= data.count {
-            if !array.ensure_capacity(^data, dest) do return .OutOfBounds;
+        switch whence {
+            case SeekFrom.Start   do curr_pos = to;
+            case SeekFrom.Current do curr_pos += to;
+            case SeekFrom.End     do curr_pos = data.count - to; // CHECK: Off by one?
         }
 
-        curr_pos = dest;
         return .None;
     },
 
-    tell = (use dss: ^DynamicStringStream) -> (Error, u32) {
+    tell = (use ss: ^BufferStream) -> (Error, u32) {
         return .None, curr_pos;
     },
 
-    read = (use dss: ^DynamicStringStream, buffer: [] u8) -> (Error, u32) {
+    read = (use ss: ^BufferStream, buffer: [] u8) -> (Error, u32) {
         if curr_pos >= data.count do return .EOF, 0;
 
         bytes_to_read := math.min(buffer.count, data.count - curr_pos);
@@ -277,7 +239,7 @@ dynamic_string_stream_vtable := Stream_Vtable.{
         return .None, bytes_to_read;
     },
 
-    read_at = (use dss: ^DynamicStringStream, at: u32, buffer: [] u8) -> (Error, u32) {
+    read_at = (use ss: ^BufferStream, at: u32, buffer: [] u8) -> (Error, u32) {
         if at >= data.count do return .EOF, 0;
 
         bytes_to_read := math.min(buffer.count, data.count - at);
@@ -286,27 +248,33 @@ dynamic_string_stream_vtable := Stream_Vtable.{
         return .None, bytes_to_read;
     },
 
-    read_byte = (use dss: ^DynamicStringStream) -> (Error, u8) {
+    read_byte = (use ss: ^BufferStream) -> (Error, u8) {
         if curr_pos >= data.count do return .EOF, 0;
 
         defer curr_pos += 1;
         return .None, data[curr_pos];
     },
 
-    write = (use dss: ^DynamicStringStream, buffer: [] u8) -> (Error, u32) {
+    write = (use dss: ^BufferStream, buffer: [] u8) -> (Error, u32) {
+        if !write_enabled do return .NotImplemented, 0;
+
         if curr_pos + buffer.count >= data.capacity {
+            if fixed do return .EOF, 0;
             if !array.ensure_capacity(^data, curr_pos + buffer.count) do return .EOF, 0;
         }
 
         memory.copy(^data.data[curr_pos], buffer.data, buffer.count);
         curr_pos   += buffer.count;
-        data.count += buffer.count;
+        data.count  = math.max(data.count, curr_pos);
 
         return .None, buffer.count;
     },
 
-    write_at = (use dss: ^DynamicStringStream, at: u32, buffer: [] u8) -> (Error, u32) {
+    write_at = (use dss: ^BufferStream, at: u32, buffer: [] u8) -> (Error, u32) {
+        if !write_enabled do return .NotImplemented, 0;
+
         if at + buffer.count >= data.capacity {
+            if fixed do return .EOF, 0;
             if !array.ensure_capacity(^data, at + buffer.count) do return .EOF, 0;
         }
 
@@ -316,24 +284,32 @@ dynamic_string_stream_vtable := Stream_Vtable.{
         return .None, buffer.count;
     },
 
-    write_byte = (use dss: ^DynamicStringStream, byte: u8) -> Error {
-        if !array.ensure_capacity(^data, data.count + 1) do return .EOF;
+    write_byte = (use dss: ^BufferStream, byte: u8) -> Error {
+        if !write_enabled do return .NotImplemented;
+
+        if curr_pos + 1 >= data.capacity {
+            if fixed do return .EOF;
+            if !array.ensure_capacity(^data, data.count + 1) do return .EOF;
+        }
 
         data[curr_pos] = byte;
         curr_pos   += 1;
-        data.count += 1;
+        data.count  = math.max(data.count, curr_pos);
 
         return .None;
     },
 
-    size = (use dss: ^DynamicStringStream) -> i32 {
+    size = (use dss: ^BufferStream) -> i32 {
         return data.count;
     },
 
-    flush = (use dss: ^DynamicStringStream) -> Error {
+    flush = (use dss: ^BufferStream) -> Error {
+        if !write_enabled || fixed do return .NotImplemented;
+
         curr_pos = 0;
         array.clear(^data);
 
         return .None;
     },
 }
+
index ed466b7e00bdaeae911426c9587d5b13dea50c7c..f545b9f0f04780b0e33c14d545053f7f019d9be8 100644 (file)
@@ -16,9 +16,9 @@ writer_make :: (s: ^Stream) -> Writer {
 // Future-proofing the API
 writer_free :: (w: ^Writer) {}
 
-string_builder :: (allocator := context.allocator) -> (Writer, ^DynamicStringStream) {
-    new_stream := new(DynamicStringStream, allocator=allocator);
-    *new_stream = dynamic_string_stream_make();
+string_builder :: (allocator := context.allocator) -> (Writer, ^BufferStream) {
+    new_stream := new(BufferStream, allocator=allocator);
+    *new_stream = buffer_stream_make();
 
     return writer_make(new_stream), new_stream;
 }
index 4d98e95c77bf7e5802e192678f069b4c547a2f91..20e2113f37ac0c2bd518c3251727c043ed9ff6da 100644 (file)
@@ -114,12 +114,12 @@ byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) {
 //
 
 #thread_local stdio : struct {
-    print_stream : io.DynamicStringStream;
+    print_stream : io.BufferStream;
     print_writer : io.Writer;
 }
 
 __stdio_init :: () {
-    stdio.print_stream = io.dynamic_string_stream_make(2048, context.allocator);
+    stdio.print_stream = io.buffer_stream_make(2048, context.allocator);
     stdio.print_writer = io.writer_make(^stdio.print_stream);
     
     // This shouldn't need to be here, but because ^stdin_vtable is not a compile-time
@@ -132,7 +132,7 @@ __flush_stdio :: () {
     if stdio.print_stream.data.count == 0 do return;
 
     ^stdio.print_stream
-        |> io.dynamic_string_stream_to_str()
+        |> io.buffer_stream_to_str()
         |> runtime.__output_string();
 
     ^stdio.print_stream |> io.stream_flush();
index b1a2a98d197b035bbe9ff5fa9222fc9bd344e4cb..828f6613ecd1a901f08e05643b7655187210889c 100644 (file)
@@ -461,6 +461,7 @@ AstNode* ast_clone(bh_allocator a, void* n) {
                 new_param.local = (AstLocal *) ast_clone(a, param->local);
                 new_param.local->flags &= ~Ast_Flag_Param_Symbol_Dirty;
                 new_param.default_value = (AstTyped *) ast_clone(a, param->default_value);
+                new_param.use_processed = 0;
                 dont_copy_structs = 0;
 
                 new_param.vararg_kind = param->vararg_kind;
@@ -605,6 +606,7 @@ AstFunction* clone_function_header(bh_allocator a, AstFunction* func) {
         new_param.local = (AstLocal *) ast_clone(a, param->local);
         new_param.local->flags &= ~Ast_Flag_Param_Symbol_Dirty;
         new_param.default_value = (AstTyped *) ast_clone(a, param->default_value);
+        new_param.use_processed = 0;
         dont_copy_structs = 0;
 
         new_param.vararg_kind = param->vararg_kind;
index ca756ae8801d041e06f697f9741581823b7115aa..f006c7648242eacb32467d9fc0052850086efb16 100644 (file)
@@ -337,17 +337,19 @@ static SymresStatus symres_field_access(AstFieldAccess** fa) {
             char *closest = find_closest_symbol_in_node((AstNode *) expr, (*fa)->token->text);
             token_toggle_end((*fa)->token);
 
+            AstPackage *package = (AstPackage *) strip_aliases((AstNode *) (*fa)->expr);
+
             if (closest) {
                 onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' was not found in package '%s'. Did you mean '%s'?",
                     (*fa)->token->text,
                     (*fa)->token->length,
-                    ((AstPackage *) (*fa)->expr)->package->name,
+                    package->package->name,
                     closest);
             } else {
                 onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' was not found in package '%s'. Perhaps it is defined in a file that wasn't loaded?",
                     (*fa)->token->text,
                     (*fa)->token->length,
-                    ((AstPackage *) (*fa)->expr)->package->name);
+                    package->package->name);
             }
             return Symres_Error;
 
index a368713184e3a6efda1559fe5f578a7b0fc24d07..0d9568a02f703d1e3fe790107fc4d599f52e0af8 100644 (file)
@@ -1112,8 +1112,10 @@ Type* type_get_contained_type(Type* type) {
 }
 
 static const StructMember slice_members[] = {
-    { 0,            0, NULL,                         "data",  NULL, NULL, -1, 0, 0 },
-    { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "count", NULL, NULL, -1, 0, 0 },
+    { 0,            0, NULL,                         "data",   NULL, NULL, -1, 0, 0 },
+    { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "count",  NULL, NULL, -1, 0, 0 },
+    { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "size",   NULL, NULL, -1, 0, 0 },
+    { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "length", NULL, NULL, -1, 0, 0 },
 };
 
 static const StructMember array_members[] = {
@@ -1121,6 +1123,8 @@ static const StructMember array_members[] = {
     { POINTER_SIZE,     1, &basic_types[Basic_Kind_U32], "count",     NULL, NULL, -1, 0, 0 },
     { POINTER_SIZE + 4, 2, &basic_types[Basic_Kind_U32], "capacity",  NULL, NULL, -1, 0, 0 },
     { POINTER_SIZE + 8, 3, NULL,                         "allocator", NULL, NULL, -1, 0, 0 },
+    { POINTER_SIZE,     1, &basic_types[Basic_Kind_U32], "size",      NULL, NULL, -1, 0, 0 },
+    { POINTER_SIZE,     1, &basic_types[Basic_Kind_U32], "length",    NULL, NULL, -1, 0, 0 },
 };
 
 b32 type_lookup_member(Type* type, char* member, StructMember* smem) {
index b8913537b601630a42618681eaa5256d51371f29..0289afc73554877413847c1e94255ccf2c1f7357 100644 (file)
@@ -43,7 +43,7 @@ combat :: (player1: ^[..] u32, player2: ^[..] u32) -> u32 {
 //   4,5,2,8,|1,3,9,7,
 // Larger numbers are encoded in base 64.
 encode_hands :: (alloc: Allocator, p1: ^[..] u32, p2: ^[..] u32) -> str {
-    stream := io.dynamic_string_stream_make(256, alloc);
+    stream := io.buffer_stream_make(256, alloc);
     writer := io.writer_make(^stream);
 
     for n: *p1 {
@@ -56,7 +56,7 @@ encode_hands :: (alloc: Allocator, p1: ^[..] u32, p2: ^[..] u32) -> str {
         io.write_str(^writer, ",");
     }
 
-    return io.dynamic_string_stream_to_str(^stream);
+    return io.buffer_stream_to_str(^stream);
 }
 
 recursive_combat :: (player1: ^[..] u32, player2: ^[..] u32) -> u32 {
index 2111109cc8aca8116ef228c2b2cb35809a505cf6..47a534cb6f2df2001ae871b0f55ca3883fb07369 100644 (file)
@@ -28,7 +28,7 @@ Cell :: struct {
 main :: (args: [] cstr) {
        contents := #file_contents "./tests/aoc-2020/input/day24.txt";
 
-    file_stream := io.string_stream_make(contents);
+    file_stream := io.buffer_stream_make(contents);
        file := io.reader_make(^file_stream);
 
        grid := map.make(Vec2, Cell, .{}); // `true` is black
index b3eb204aaa1afd33be817e8f9ef54eb2fd342586..9f722810921340d9f7093ffe3f9b3f443608c465 100644 (file)
@@ -5,7 +5,7 @@ use package core
 main :: (args: [] cstr) {
     some_string := "This is a test string that can be read.\n\n\n\n\n\n1234 4567";
 
-    sstream := io.string_stream_make(some_string);
+    sstream := io.buffer_stream_make(some_string);
     sreader := io.reader_make(^sstream);
 
     for i: 0 .. 2 {