added: bi-directional custom serializer
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 16 Mar 2023 14:42:41 +0000 (09:42 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 16 Mar 2023 14:42:41 +0000 (09:42 -0500)
core/encoding/osad.onyx [new file with mode: 0644]
core/std.onyx
tests/osad_test [new file with mode: 0644]
tests/osad_test.onyx [new file with mode: 0644]

diff --git a/core/encoding/osad.onyx b/core/encoding/osad.onyx
new file mode 100644 (file)
index 0000000..9ac3ba8
--- /dev/null
@@ -0,0 +1,224 @@
+package core.encoding.osad
+
+
+use core {io, string, array, memory}
+use runtime {
+    type_info :: info
+}
+
+//
+// Onyx Serialize and Deseralize
+//
+
+
+//
+// Serialize
+//
+
+
+serialize :: #match #local {}
+
+#overload
+serialize :: (v: any) -> ? [] u8 {
+    writer, stream := io.string_builder();
+    defer cfree(stream);
+
+    if !serialize(any.{v.data, v.type}, &writer) {
+        delete(stream);
+        return .{};
+    }
+
+    return string.as_str(stream);
+}
+
+#overload
+serialize :: (v: any, w: &io.Writer) -> bool {
+    info := type_info.get_type_info(v.type);
+
+    switch info.kind {
+        // Cannot serialize a pointer.
+        case .Pointer {
+            log(.Debug, "Core", "OSAD: Cannot serialize a pointer.");
+            return false;
+        }
+
+        case .Basic {
+            if v.type == rawptr {
+                log(.Debug, "Core", "OSAD: Cannot serialize a pointer.");
+                return false;
+            }
+
+            // Output the raw bytes of the primitive type.
+            io.write_str(w, str.{ v.data, info.size });
+        }
+
+        case .Function {
+            log(.Debug, "Core", "OSAD: Cannot serialize a function.");
+            return false;
+        }
+
+        case .Array {
+            a_info := cast(&type_info.Type_Info_Array, info);
+
+            base: &u8 = ~~v.data;
+            elem_size := type_info.size_of(a_info.of);
+
+            for a_info.count {
+                try(serialize(any.{base + elem_size * it, a_info.of}, w));
+            }
+        }
+
+        case .Slice, .Dynamic_Array {
+            s_info := cast(&type_info.Type_Info_Slice, info);
+
+            untyped_slice := cast(&array.Untyped_Array, v.data);
+            base: &u8 = untyped_slice.data;
+            count:    = untyped_slice.count;
+            elem_size := type_info.size_of(s_info.of);
+
+            output_u32(w, count);
+            for count {
+                try(serialize(any.{base + elem_size * it, s_info.of}, w));
+            }
+        }
+
+        case .Enum {
+            e_info := cast(&type_info.Type_Info_Enum, info);
+            try(serialize(any.{ v.data, e_info.backing_type }, w));
+        }
+
+        case .Distinct {
+            d_info := cast(&type_info.Type_Info_Distinct, info);
+            try(serialize(any.{ v.data, d_info.base_type }, w));
+        }
+
+        case .Struct {
+            s_info := cast(&type_info.Type_Info_Struct, info);
+
+            base: &u8 = ~~v.data;
+
+            for& member: s_info.members {
+                try(serialize(any.{ base + member.offset, member.type }, w));
+            }
+        }
+    }
+
+    return true;
+
+
+    output_u32 :: macro (w: &io.Writer, i: u32) {
+        v := i;
+        io.write_str(w, str.{ ~~&v, 4 });
+    }
+}
+
+
+//
+// Deserialize
+//
+
+deserialize :: #match #local {}
+
+#overload
+deserialize :: ($T: type_expr, s: str) -> ?T {
+    reader, stream := io.reader_from_string(s);
+    defer cfree(stream);
+
+    target: T;
+    if !deserialize(&target, T, &reader) {
+        // This could leak memory if we partially deserialized something.
+        return .{};
+    }
+
+    return target;
+}
+
+#overload
+deserialize :: (target: rawptr, type: type_expr, r: &io.Reader) -> bool {
+    info := type_info.get_type_info(type);
+
+    switch info.kind {
+        case .Pointer {
+            log(.Debug, "Core", "OSAD: Cannot deserialize a pointer.");
+            return false;
+        }
+
+        case .Function {
+            log(.Debug, "Core", "OSAD: Cannot deserialize a function.");
+            return false;
+        }
+
+        case .Basic {
+            bytes, err := io.read_bytes(r, .{ target, info.size });
+            assert(err == .None, "Deserialize expected to be able to read all the bytes.");
+        }
+
+        case .Enum {
+            e_info := cast(&type_info.Type_Info_Enum, info);
+            try(deserialize(target, e_info.backing_type, r));
+        }
+
+        case .Distinct {
+            d_info := cast(&type_info.Type_Info_Distinct, info);
+            try(deserialize(target, d_info.base_type, r));
+        }
+
+        case .Array {
+            a_info := cast(&type_info.Type_Info_Array, info);
+
+            base: &u8 = target;
+            elem_size := type_info.size_of(a_info.of);
+
+            for a_info.count {
+                try(deserialize(base + it * elem_size, a_info.of, r));
+            }
+        }
+
+        case .Slice, .Dynamic_Array {
+            s_info := cast(&type_info.Type_Info_Slice, info);
+            elem_size := type_info.size_of(s_info.of);
+
+            count := read_u32(r);
+
+            untyped_slice := cast(&array.Untyped_Array, target);
+            untyped_slice.count = count;
+            untyped_slice.data = raw_alloc(context.allocator, elem_size * count);
+            memory.set(untyped_slice.data, 0, elem_size * count);
+
+            if info.kind == .Dynamic_Array {
+                untyped_slice.capacity = count;
+                untyped_slice.allocator = context.allocator;
+            }
+
+            base: &u8 = untyped_slice.data;
+
+            for count {
+                try(deserialize(base + it * elem_size, s_info.of, r));
+            }
+        }
+
+        case .Struct {
+            s_info := cast(&type_info.Type_Info_Struct, info);
+
+            base: &u8 = target;
+
+            for& member: s_info.members {
+                try(deserialize(base + member.offset, member.type, r));
+            }
+        }
+    }
+
+    return true;
+
+    read_u32 :: macro (r: &io.Reader) -> u32 {
+        dest: u32;
+        io.read_bytes(r, str.{~~&dest, 4});
+        return dest;
+    }
+}
+
+
+#local
+try :: macro (x: $T) {
+    if !x do return false;
+}
index 24ab407501ebf8c5be309b50ba49291a18381dc0..d33b78ad66f57182d6973cfff641c9c8b4d0811f 100644 (file)
@@ -52,6 +52,7 @@ package core
 #load "./time/date"
 #load "./encoding/base64"
 #load "./encoding/utf8"
+#load "./encoding/osad"
 
 #load "./runtime/common"
 
diff --git a/tests/osad_test b/tests/osad_test
new file mode 100644 (file)
index 0000000..b98b98b
--- /dev/null
@@ -0,0 +1,76 @@
+Foo { 
+    nums = [
+        2, 
+        3, 
+        5, 
+        7, 
+        11
+    ], 
+    bar = Bar { 
+        name = "Joe", 
+        foos = [
+            Foo { 
+                nums = [
+                    1, 
+                    2, 
+                    3
+                ], 
+                bar = Bar { 
+                    name = "", 
+                    foos = [
+                    ]
+                }
+            }, 
+            Foo { 
+                nums = [
+                    4, 
+                    5, 
+                    6
+                ], 
+                bar = Bar { 
+                    name = "", 
+                    foos = [
+                    ]
+                }
+            }
+        ]
+    }
+}
+Foo { 
+    nums = [
+        2, 
+        3, 
+        5, 
+        7, 
+        11
+    ], 
+    bar = Bar { 
+        name = "Joe", 
+        foos = [
+            Foo { 
+                nums = [
+                    1, 
+                    2, 
+                    3
+                ], 
+                bar = Bar { 
+                    name = "", 
+                    foos = [
+                    ]
+                }
+            }, 
+            Foo { 
+                nums = [
+                    4, 
+                    5, 
+                    6
+                ], 
+                bar = Bar { 
+                    name = "", 
+                    foos = [
+                    ]
+                }
+            }
+        ]
+    }
+}
diff --git a/tests/osad_test.onyx b/tests/osad_test.onyx
new file mode 100644 (file)
index 0000000..6bf1882
--- /dev/null
@@ -0,0 +1,33 @@
+use core
+use core.encoding {osad}
+
+Foo :: struct {
+    nums: [] i32;
+    bar: Bar;
+}
+
+Bar :: struct {
+    name: str;
+    foos: [] Foo;
+}
+
+main :: () {
+    f := Foo.{
+        nums = .[2, 3, 5, 7, 11],
+        bar = .{
+            name = "Joe",
+            foos = .[
+                .{ .[ 1, 2, 3 ], .{} },
+                .{ .[ 4, 5, 6 ], .{} },
+            ]
+        }
+    };
+
+    s := osad.serialize(f)->unwrap();
+
+    new_f := osad.deserialize(Foo, s)->unwrap();
+
+
+    printf("{p}\n", f);
+    printf("{p}\n", new_f);
+}