Merge pull request #3886 from laytan/cbor-enhancements

CBOR enhancements
This commit is contained in:
Jeroen van Rijn
2024-07-08 02:01:39 +02:00
committed by GitHub
4 changed files with 95 additions and 39 deletions
+33 -5
View File
@@ -77,8 +77,11 @@ You can look at the default tags provided for pointers on how these implementati
Example:
package main
import "base:intrinsics"
import "core:encoding/cbor"
import "core:fmt"
import "core:reflect"
import "core:time"
Possibilities :: union {
@@ -93,9 +96,32 @@ Example:
ignore_this: ^Data `cbor:"-"`, // Ignored by implementation.
renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded.
my_union: Possibilities, // Union support.
my_raw: [8]u32 `cbor_tag:"raw"`, // Custom tag that just writes the value as bytes.
}
main :: proc() {
// Example custom tag implementation that instead of breaking down all parts,
// just writes the value as a big byte blob. This is an advanced feature but very powerful.
RAW_TAG_NR :: 200
cbor.tag_register_number({
marshal = proc(_: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error {
cbor._encode_u8(e.writer, RAW_TAG_NR, .Tag) or_return
return cbor.err_conv(cbor._encode_bytes(e, reflect.as_bytes(v)))
},
unmarshal = proc(_: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) {
hdr := cbor._decode_header(d.reader) or_return
maj, add := cbor._header_split(hdr)
if maj != .Bytes {
return .Bad_Tag_Value
}
bytes := cbor.err_conv(cbor._decode_bytes(d, add, maj)) or_return
intrinsics.mem_copy_non_overlapping(v.data, raw_data(bytes), len(bytes))
return nil
},
}, RAW_TAG_NR, "raw")
now := time.Time{_nsec = 1701117968 * 1e9}
data := Data{
@@ -105,21 +131,22 @@ Example:
ignore_this = &Data{},
renamed = 123123.125,
my_union = 3,
my_raw = {1=1, 2=2, 3=3},
}
// Marshal the struct into binary CBOR.
binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC)
assert(err == nil)
fmt.assertf(err == nil, "marshal error: %v", err)
defer delete(binary)
// Decode the binary data into a `cbor.Value`.
decoded, derr := cbor.decode(string(binary))
assert(derr == nil)
fmt.assertf(derr == nil, "decode error: %v", derr)
defer cbor.destroy(decoded)
// Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]].
diagnosis, eerr := cbor.to_diagnostic_format(decoded)
assert(eerr == nil)
fmt.assertf(eerr == nil, "to diagnostic error: %v", eerr)
defer delete(diagnosis)
fmt.println(diagnosis)
@@ -127,6 +154,7 @@ Example:
Output:
{
"my_raw": 200(h'00001000200030000000000000000000'),
"my_union": 1010([
"int",
3
+56 -16
View File
@@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
defer if err != nil { strings.builder_destroy(&b) }
if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil {
if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
return
}
@@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
// Marshals the given value into a CBOR byte stream written to the given builder.
// See docs on the `marshal_into` proc group for more info.
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc)
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator)
}
// Marshals the given value into a CBOR byte stream written to the given writer.
// See docs on the `marshal_into` proc group for more info.
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
encoder := Encoder{flags, w, temp_allocator}
return marshal_into_encoder(encoder, v, loc=loc)
return marshal_into_encoder(encoder, v)
}
// Marshals the given value into a CBOR byte stream written to the given encoder.
// See docs on the `marshal_into` proc group for more info.
marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (err: Marshal_Error) {
marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
e := e
if e.temp_allocator.procedure == nil {
@@ -97,11 +97,14 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
return impl->marshal(e, v)
}
ti := runtime.type_info_base(type_info_of(v.id))
a := any{v.data, ti.id}
ti := runtime.type_info_core(type_info_of(v.id))
return _marshal_into_encoder(e, v, ti)
}
_marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (err: Marshal_Error) {
a := any{v.data, ti.id}
#partial switch info in ti.variant {
case runtime.Type_Info_Named:
case runtime.Type_Info_Named, runtime.Type_Info_Enum, runtime.Type_Info_Bit_Field:
unreachable()
case runtime.Type_Info_Pointer:
@@ -223,18 +226,38 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
}
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
case runtime.Type_Info_Enumerated_Array:
// index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum)
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
@@ -246,9 +269,19 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
array := (^mem.Raw_Dynamic_Array)(v.data)
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<array.len {
data := uintptr(array.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<array.len {
data := uintptr(array.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
@@ -260,9 +293,19 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
array := (^mem.Raw_Slice)(v.data)
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<array.len {
data := uintptr(array.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<array.len {
data := uintptr(array.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
@@ -542,9 +585,6 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
return marshal_into(e, any{v.data, vti.id})
case runtime.Type_Info_Enum:
return marshal_into(e, any{v.data, info.base.id})
case runtime.Type_Info_Bit_Set:
// Store bit_set as big endian just like the protocol.
do_byte_swap := !reflect.bit_set_is_big_endian(v)
+6 -14
View File
@@ -520,9 +520,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
return
case reflect.Type_Info_Array:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, t.count)
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > t.count {
return _unsupported(v, hdr)
}
@@ -534,9 +532,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
return
case reflect.Type_Info_Enumerated_Array:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, t.count)
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > t.count {
return _unsupported(v, hdr)
}
@@ -548,9 +544,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
return
case reflect.Type_Info_Complex:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, 2)
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > 2 {
return _unsupported(v, hdr)
}
@@ -570,9 +564,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
return
case reflect.Type_Info_Quaternion:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, 4)
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > 4 {
return _unsupported(v, hdr)
}
@@ -633,7 +625,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
length, _ := err_conv(_decode_len_container(d, add)) or_return
unknown := length == -1
fields := reflect.struct_fields_zipped(ti.id)
for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 {
// Decode key, keys can only be strings.
key: string
@@ -646,7 +638,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
key = keyv
}
defer delete(key, context.temp_allocator)
// Find matching field.
use_field_idx := -1
{
-4
View File
@@ -375,10 +375,6 @@ write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error)
nn, err = write(w, buf[n:])
n += nn
}
if err == nil && n < min {
err = .Short_Write
}
return
}