Merge remote-tracking branch 'offical/master'

This commit is contained in:
2024-04-24 10:08:37 -04:00
76 changed files with 5424 additions and 557 deletions
+2 -2
View File
@@ -40,7 +40,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
}
return n
}
// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`.
// `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`.
// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
// of len(src) and len(dst).
//
@@ -53,7 +53,7 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int
}
return n
}
// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`.
// `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`.
// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
// of len(src) and len(dst).
@builtin
+13 -1
View File
@@ -19,6 +19,7 @@ type_assertion_trap :: proc "contextless" () -> ! {
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
if uint(index) < uint(count) {
return
@@ -61,6 +62,7 @@ multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, colu
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) {
if lo <= hi {
return
@@ -68,6 +70,7 @@ multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column
multi_pointer_slice_handle_error(file, line, column, lo, hi)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
if 0 <= hi && hi <= len {
return
@@ -75,6 +78,7 @@ slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi:
slice_handle_error(file, line, column, 0, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) {
if 0 <= lo && lo <= len && lo <= hi && hi <= len {
return
@@ -82,6 +86,7 @@ slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, l
slice_handle_error(file, line, column, lo, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
if 0 <= low && low <= high && high <= max {
return
@@ -102,6 +107,7 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32,
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
if uint(row_index) < uint(row_count) &&
uint(column_index) < uint(column_count) {
@@ -224,6 +230,7 @@ when ODIN_NO_RTTI {
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) {
if 0 <= len {
return
@@ -239,6 +246,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio
handle_error(loc, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
if 0 <= len && len <= cap {
return
@@ -256,6 +264,7 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller
handle_error(loc, len, cap)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) {
if 0 <= cap {
return
@@ -274,19 +283,22 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca
@(disabled=ODIN_NO_BOUNDS_CHECK)
bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) {
bounds_check_error(loc.file_path, loc.line, loc.column, index, count)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) {
slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) {
slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) {
dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max)
}
+22 -16
View File
@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
when !ODIN_NO_RTTI {
print_any_single :: proc "contextless" (arg: any) {
print_any_single :: #force_no_inline proc "contextless" (arg: any) {
x := arg
if x.data == nil {
print_string("nil")
@@ -72,7 +72,7 @@ when !ODIN_NO_RTTI {
print_string("<invalid-value>")
}
}
println_any :: proc "contextless" (args: ..any) {
println_any :: #force_no_inline proc "contextless" (args: ..any) {
context = default_context()
loop: for arg, i in args {
assert(arg.id != nil)
@@ -122,12 +122,12 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
return buf, 4
}
print_string :: proc "contextless" (str: string) -> (n: int) {
print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) {
n, _ = stderr_write(transmute([]byte)str)
return
}
print_strings :: proc "contextless" (args: ..string) -> (n: int) {
print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) {
for str in args {
m, err := stderr_write(transmute([]byte)str)
n += m
@@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) {
return
}
print_byte :: proc "contextless" (b: byte) -> (n: int) {
print_byte :: #force_no_inline proc "contextless" (b: byte) -> (n: int) {
n, _ = stderr_write([]byte{b})
return
}
print_encoded_rune :: proc "contextless" (r: rune) {
print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) {
print_byte('\'')
switch r {
@@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
print_byte('\'')
}
print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_check {
RUNE_SELF :: 0x80
if r < RUNE_SELF {
@@ -183,7 +183,7 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
}
print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check {
a: [129]byte
i := len(a)
b := u64(10)
@@ -198,7 +198,7 @@ print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
}
print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check {
b :: i64(10)
u := x
@@ -223,25 +223,29 @@ print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) }
print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) }
print_int :: proc "contextless" (x: int) { print_i64(i64(x)) }
print_caller_location :: proc "contextless" (loc: Source_Code_Location) {
print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_Location) {
print_string(loc.file_path)
when ODIN_ERROR_POS_STYLE == .Default {
print_byte('(')
print_u64(u64(loc.line))
print_byte(':')
print_u64(u64(loc.column))
if loc.column != 0 {
print_byte(':')
print_u64(u64(loc.column))
}
print_byte(')')
} else when ODIN_ERROR_POS_STYLE == .Unix {
print_byte(':')
print_u64(u64(loc.line))
print_byte(':')
print_u64(u64(loc.column))
if loc.column != 0 {
print_byte(':')
print_u64(u64(loc.column))
}
print_byte(':')
} else {
#panic("unhandled ODIN_ERROR_POS_STYLE")
}
}
print_typeid :: proc "contextless" (id: typeid) {
print_typeid :: #force_no_inline proc "contextless" (id: typeid) {
when ODIN_NO_RTTI {
if id == nil {
print_string("nil")
@@ -257,7 +261,9 @@ print_typeid :: proc "contextless" (id: typeid) {
}
}
}
print_type :: proc "contextless" (ti: ^Type_Info) {
@(optimization_mode="size")
print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
if ti == nil {
print_string("nil")
return
+21 -24
View File
@@ -1,34 +1,31 @@
/*
package demo
Example:
package demo
import tokenizer "core:c/frontend/tokenizer"
import preprocessor "core:c/frontend/preprocessor"
import "core:fmt"
import tokenizer "core:c/frontend/tokenizer"
import preprocessor "core:c/frontend/preprocessor"
import "core:fmt"
main :: proc() {
t := &tokenizer.Tokenizer{};
tokenizer.init_defaults(t);
main :: proc() {
t := &tokenizer.Tokenizer{};
tokenizer.init_defaults(t);
cpp := &preprocessor.Preprocessor{};
cpp.warn, cpp.err = t.warn, t.err;
preprocessor.init_lookup_tables(cpp);
preprocessor.init_default_macros(cpp);
cpp.include_paths = {"my/path/to/include"};
cpp := &preprocessor.Preprocessor{};
cpp.warn, cpp.err = t.warn, t.err;
preprocessor.init_lookup_tables(cpp);
preprocessor.init_default_macros(cpp);
cpp.include_paths = {"my/path/to/include"};
tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
tok = preprocessor.preprocess(cpp, tok);
if tok != nil {
for t := tok; t.kind != .EOF; t = t.next {
fmt.println(t.lit);
tok = preprocessor.preprocess(cpp, tok);
if tok != nil {
for t := tok; t.kind != .EOF; t = t.next {
fmt.println(t.lit);
}
}
fmt.println("[Done]");
}
fmt.println("[Done]");
}
*/
package c_frontend_tokenizer
+1 -1
View File
@@ -1,5 +1,5 @@
//+build ignore
package gzip
package compress_gzip
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -1,4 +1,4 @@
package gzip
package compress_gzip
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -5,7 +5,7 @@
*/
// package shoco is an implementation of the shoco short string compressor
package shoco
package compress_shoco
DEFAULT_MODEL :: Shoco_Model {
min_char = 39,
+1 -1
View File
@@ -9,7 +9,7 @@
*/
// package shoco is an implementation of the shoco short string compressor
package shoco
package compress_shoco
import "base:intrinsics"
import "core:compress"
+1 -1
View File
@@ -1,5 +1,5 @@
//+build ignore
package zlib
package compress_zlib
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -1,5 +1,5 @@
//+vet !using-param
package zlib
package compress_zlib
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -1,4 +1,4 @@
package dynamic_bit_array
package container_dynamic_bit_array
import "base:intrinsics"
import "core:mem"
+40 -41
View File
@@ -1,53 +1,52 @@
package dynamic_bit_array
/*
The Bit Array can be used in several ways:
The Bit Array can be used in several ways:
-- By default you don't need to instantiate a Bit Array:
- By default you don't need to instantiate a Bit Array:
package test
package test
import "core:fmt"
import "core:container/bit_array"
import "core:fmt"
import "core:container/bit_array"
main :: proc() {
using bit_array
main :: proc() {
using bit_array
bits: Bit_Array
bits: Bit_Array
// returns `true`
fmt.println(set(&bits, 42))
// returns `true`
fmt.println(set(&bits, 42))
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
was_set, was_retrieved := get(&bits, -1)
fmt.println(was_set, was_retrieved)
destroy(&bits)
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
was_set, was_retrieved := get(&bits, -1)
fmt.println(was_set, was_retrieved)
destroy(&bits)
}
- A Bit Array can optionally allow for negative indices, if the minimum value was given during creation:
package test
import "core:fmt"
import "core:container/bit_array"
main :: proc() {
Foo :: enum int {
Negative_Test = -42,
Bar = 420,
Leaves = 69105,
}
-- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation:
using bit_array
package test
bits := create(int(max(Foo)), int(min(Foo)))
defer destroy(bits)
import "core:fmt"
import "core:container/bit_array"
main :: proc() {
Foo :: enum int {
Negative_Test = -42,
Bar = 420,
Leaves = 69105,
}
using bit_array
bits := create(int(max(Foo)), int(min(Foo)))
defer destroy(bits)
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
fmt.printf("Freed.\n")
}
*/
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
fmt.printf("Freed.\n")
}
*/
package container_dynamic_bit_array
+2 -3
View File
@@ -1,6 +1,5 @@
//+build ignore
/*
Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
Package `core:dynlib` implements loading of shared libraries/DLLs and their symbols.
The behaviour of dynamically loaded libraries is specific to the target platform of the program.
For in depth detail on the underlying behaviour please refer to your target platform's documentation.
@@ -8,4 +7,4 @@ For in depth detail on the underlying behaviour please refer to your target plat
See `example` directory for an example library exporting 3 symbols and a host program loading them automatically
by defining a symbol table struct.
*/
package dynlib
package dynlib
+1 -1
View File
@@ -135,7 +135,7 @@ initialize_symbols :: proc(
prefixed_symbol_buf: [2048]u8 = ---
count = 0
for field, i in reflect.struct_fields_zipped(T) {
for field in reflect.struct_fields_zipped(T) {
// Calculate address of struct member
field_ptr := rawptr(uintptr(symbol_table) + field.offset)
+1 -1
View File
@@ -1,4 +1,4 @@
package base32
package encoding_base32
// @note(zh): Encoding utility for Base32
// A secondary param can be used to supply a custom alphabet to
+125 -48
View File
@@ -1,4 +1,8 @@
package base64
package encoding_base64
import "core:io"
import "core:mem"
import "core:strings"
// @note(zh): Encoding utility for Base64
// A secondary param can be used to supply a custom alphabet to
@@ -39,59 +43,132 @@ DEC_TABLE := [128]int {
49, 50, 51, -1, -1, -1, -1, -1,
}
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string #no_bounds_check {
length := len(data)
if length == 0 {
return ""
}
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error {
out_length := encoded_len(data)
if out_length == 0 {
return
}
out_length := ((4 * length / 3) + 3) &~ 3
out := make([]byte, out_length, allocator)
out := strings.builder_make(0, out_length, allocator) or_return
ioerr := encode_into(strings.to_stream(&out), data, ENC_TBL)
c0, c1, c2, block: int
assert(ioerr == nil, "string builder should not IO error")
assert(strings.builder_cap(out) == out_length, "buffer resized, `encoded_len` was wrong")
for i, d := 0, 0; i < length; i, d = i + 3, d + 4 {
c0, c1, c2 = int(data[i]), -1, -1
if i + 1 < length { c1 = int(data[i + 1]) }
if i + 2 < length { c2 = int(data[i + 2]) }
block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0)
out[d] = ENC_TBL[block >> 18 & 63]
out[d + 1] = ENC_TBL[block >> 12 & 63]
out[d + 2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63]
out[d + 3] = c2 == -1 ? PADDING : ENC_TBL[block & 63]
}
return string(out)
return strings.to_string(out), nil
}
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check {
length := len(data)
if length == 0 {
return nil
}
encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> io.Error {
length := len(data)
if length == 0 {
return nil
}
pad_count := data[length - 1] == PADDING ? (data[length - 2] == PADDING ? 2 : 1) : 0
out_length := ((length * 6) >> 3) - pad_count
out := make([]byte, out_length, allocator)
c0, c1, c2, block: int
out: [4]byte
for i := 0; i < length; i += 3 {
#no_bounds_check {
c0, c1, c2 = int(data[i]), -1, -1
c0, c1, c2, c3: int
b0, b1, b2: int
if i + 1 < length { c1 = int(data[i + 1]) }
if i + 2 < length { c2 = int(data[i + 2]) }
for i, j := 0, 0; i < length; i, j = i + 4, j + 3 {
c0 = DEC_TBL[data[i]]
c1 = DEC_TBL[data[i + 1]]
c2 = DEC_TBL[data[i + 2]]
c3 = DEC_TBL[data[i + 3]]
b0 = (c0 << 2) | (c1 >> 4)
b1 = (c1 << 4) | (c2 >> 2)
b2 = (c2 << 6) | c3
out[j] = byte(b0)
out[j + 1] = byte(b1)
out[j + 2] = byte(b2)
}
return out
block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0)
out[0] = ENC_TBL[block >> 18 & 63]
out[1] = ENC_TBL[block >> 12 & 63]
out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63]
out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63]
}
io.write_full(w, out[:]) or_return
}
return nil
}
encoded_len :: proc(data: []byte) -> int {
length := len(data)
if length == 0 {
return 0
}
return ((4 * length / 3) + 3) &~ 3
}
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (decoded: []byte, err: mem.Allocator_Error) #optional_allocator_error {
out_length := decoded_len(data)
out := strings.builder_make(0, out_length, allocator) or_return
ioerr := decode_into(strings.to_stream(&out), data, DEC_TBL)
assert(ioerr == nil, "string builder should not IO error")
assert(strings.builder_cap(out) == out_length, "buffer resized, `decoded_len` was wrong")
return out.buf[:], nil
}
decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Error {
length := decoded_len(data)
if length == 0 {
return nil
}
c0, c1, c2, c3: int
b0, b1, b2: int
buf: [3]byte
i, j: int
for ; j + 3 <= length; i, j = i + 4, j + 3 {
#no_bounds_check {
c0 = DEC_TBL[data[i]]
c1 = DEC_TBL[data[i + 1]]
c2 = DEC_TBL[data[i + 2]]
c3 = DEC_TBL[data[i + 3]]
b0 = (c0 << 2) | (c1 >> 4)
b1 = (c1 << 4) | (c2 >> 2)
b2 = (c2 << 6) | c3
buf[0] = byte(b0)
buf[1] = byte(b1)
buf[2] = byte(b2)
}
io.write_full(w, buf[:]) or_return
}
rest := length - j
if rest > 0 {
#no_bounds_check {
c0 = DEC_TBL[data[i]]
c1 = DEC_TBL[data[i + 1]]
c2 = DEC_TBL[data[i + 2]]
b0 = (c0 << 2) | (c1 >> 4)
b1 = (c1 << 4) | (c2 >> 2)
}
switch rest {
case 1: io.write_byte(w, byte(b0)) or_return
case 2: io.write_full(w, {byte(b0), byte(b1)}) or_return
}
}
return nil
}
decoded_len :: proc(data: string) -> int {
length := len(data)
if length == 0 {
return 0
}
padding: int
if data[length - 1] == PADDING {
if length > 1 && data[length - 2] == PADDING {
padding = 2
} else {
padding = 1
}
}
return ((length * 6) >> 3) - padding
}
+673
View File
@@ -0,0 +1,673 @@
package encoding_cbor
import "base:intrinsics"
import "core:encoding/json"
import "core:io"
import "core:mem"
import "core:strconv"
import "core:strings"
// If we are decoding a stream of either a map or list, the initial capacity will be this value.
INITIAL_STREAMED_CONTAINER_CAPACITY :: 8
// If we are decoding a stream of either text or bytes, the initial capacity will be this value.
INITIAL_STREAMED_BYTES_CAPACITY :: 16
// The default maximum amount of bytes to allocate on a buffer/container at once to prevent
// malicious input from causing massive allocations.
DEFAULT_MAX_PRE_ALLOC :: mem.Kilobyte
// Known/common headers are defined, undefined headers can still be valid.
// Higher 3 bits is for the major type and lower 5 bits for the additional information.
Header :: enum u8 {
U8 = (u8(Major.Unsigned) << 5) | u8(Add.One_Byte),
U16 = (u8(Major.Unsigned) << 5) | u8(Add.Two_Bytes),
U32 = (u8(Major.Unsigned) << 5) | u8(Add.Four_Bytes),
U64 = (u8(Major.Unsigned) << 5) | u8(Add.Eight_Bytes),
Neg_U8 = (u8(Major.Negative) << 5) | u8(Add.One_Byte),
Neg_U16 = (u8(Major.Negative) << 5) | u8(Add.Two_Bytes),
Neg_U32 = (u8(Major.Negative) << 5) | u8(Add.Four_Bytes),
Neg_U64 = (u8(Major.Negative) << 5) | u8(Add.Eight_Bytes),
False = (u8(Major.Other) << 5) | u8(Add.False),
True = (u8(Major.Other) << 5) | u8(Add.True),
Nil = (u8(Major.Other) << 5) | u8(Add.Nil),
Undefined = (u8(Major.Other) << 5) | u8(Add.Undefined),
Simple = (u8(Major.Other) << 5) | u8(Add.One_Byte),
F16 = (u8(Major.Other) << 5) | u8(Add.Two_Bytes),
F32 = (u8(Major.Other) << 5) | u8(Add.Four_Bytes),
F64 = (u8(Major.Other) << 5) | u8(Add.Eight_Bytes),
Break = (u8(Major.Other) << 5) | u8(Add.Break),
}
// The higher 3 bits of the header which denotes what type of value it is.
Major :: enum u8 {
Unsigned,
Negative,
Bytes,
Text,
Array,
Map,
Tag,
Other,
}
// The lower 3 bits of the header which denotes additional information for the type of value.
Add :: enum u8 {
False = 20,
True = 21,
Nil = 22,
Undefined = 23,
One_Byte = 24,
Two_Bytes = 25,
Four_Bytes = 26,
Eight_Bytes = 27,
Length_Unknown = 31,
Break = Length_Unknown,
}
Value :: union {
u8,
u16,
u32,
u64,
Negative_U8,
Negative_U16,
Negative_U32,
Negative_U64,
// Pointers so the size of the Value union stays small.
^Bytes,
^Text,
^Array,
^Map,
^Tag,
Simple,
f16,
f32,
f64,
bool,
Undefined,
Nil,
}
Bytes :: []byte
Text :: string
Array :: []Value
Map :: []Map_Entry
Map_Entry :: struct {
key: Value, // Can be any unsigned, negative, float, Simple, bool, Text.
value: Value,
}
Tag :: struct {
number: Tag_Number,
value: Value, // Value based on the number.
}
Tag_Number :: u64
Nil :: distinct rawptr
Undefined :: distinct rawptr
// A distinct atom-like number, range from `0..=19` and `32..=max(u8)`.
Simple :: distinct u8
Atom :: Simple
Unmarshal_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Decode_Data_Error,
Unmarshal_Data_Error,
Maybe(Unsupported_Type_Error),
}
Marshal_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Encode_Data_Error,
Marshal_Data_Error,
Maybe(Unsupported_Type_Error),
}
Decode_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Decode_Data_Error,
}
Encode_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Encode_Data_Error,
}
Decode_Data_Error :: enum {
None,
Bad_Major, // An invalid major type was encountered.
Bad_Argument, // A general unexpected value (most likely invalid additional info in header).
Bad_Tag_Value, // When the type of value for the given tag is not valid.
Nested_Indefinite_Length, // When an streamed/indefinite length container nests another, this is not allowed.
Nested_Tag, // When a tag's value is another tag, this is not allowed.
Length_Too_Big, // When the length of a container (map, array, bytes, string) is more than `max(int)`.
Disallowed_Streaming, // When the `.Disallow_Streaming` flag is set and a streaming header is encountered.
Break, // When the `break` header was found without any stream to break off.
}
Encode_Data_Error :: enum {
None,
Invalid_Simple, // When a simple is being encoded that is out of the range `0..=19` and `32..=max(u8)`.
Int_Too_Big, // When an int is being encoded that is larger than `max(u64)` or smaller than `min(u64)`.
Bad_Tag_Value, // When the type of value is not supported by the tag implementation.
}
Unmarshal_Data_Error :: enum {
None,
Invalid_Parameter, // When the given `any` can not be unmarshalled into.
Non_Pointer_Parameter, // When the given `any` is not a pointer.
}
Marshal_Data_Error :: enum {
None,
Invalid_CBOR_Tag, // When the struct tag `cbor_tag:""` is not a registered name or number.
}
// Error that is returned when a type couldn't be marshalled into or out of, as much information
// as possible/available is added.
Unsupported_Type_Error :: struct {
id: typeid,
hdr: Header,
add: Add,
}
_unsupported :: proc(v: any, hdr: Header, add: Add = nil) -> Maybe(Unsupported_Type_Error) {
return Unsupported_Type_Error{
id = v.id,
hdr = hdr,
add = add,
}
}
// Actual value is `-1 - x` (be careful of overflows).
Negative_U8 :: distinct u8
Negative_U16 :: distinct u16
Negative_U32 :: distinct u32
Negative_U64 :: distinct u64
// Turns the CBOR negative unsigned int type into a signed integer type.
negative_to_int :: proc {
negative_u8_to_int,
negative_u16_to_int,
negative_u32_to_int,
negative_u64_to_int,
}
negative_u8_to_int :: #force_inline proc(u: Negative_U8) -> i16 {
return -1 - i16(u)
}
negative_u16_to_int :: #force_inline proc(u: Negative_U16) -> i32 {
return -1 - i32(u)
}
negative_u32_to_int :: #force_inline proc(u: Negative_U32) -> i64 {
return -1 - i64(u)
}
negative_u64_to_int :: #force_inline proc(u: Negative_U64) -> i128 {
return -1 - i128(u)
}
// Utility for converting between the different errors when they are subsets of the other.
err_conv :: proc {
encode_to_marshal_err,
encode_to_marshal_err_p2,
decode_to_unmarshal_err,
decode_to_unmarshal_err_p,
decode_to_unmarshal_err_p2,
}
encode_to_marshal_err :: #force_inline proc(err: Encode_Error) -> Marshal_Error {
switch e in err {
case nil: return nil
case io.Error: return e
case mem.Allocator_Error: return e
case Encode_Data_Error: return e
case: return nil
}
}
encode_to_marshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Encode_Error) -> (T, T2, Marshal_Error) {
return v, v2, err_conv(err)
}
decode_to_unmarshal_err :: #force_inline proc(err: Decode_Error) -> Unmarshal_Error {
switch e in err {
case nil: return nil
case io.Error: return e
case mem.Allocator_Error: return e
case Decode_Data_Error: return e
case: return nil
}
}
decode_to_unmarshal_err_p :: #force_inline proc(v: $T, err: Decode_Error) -> (T, Unmarshal_Error) {
return v, err_conv(err)
}
decode_to_unmarshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Decode_Error) -> (T, T2, Unmarshal_Error) {
return v, v2, err_conv(err)
}
// Recursively frees all memory allocated when decoding the passed value.
destroy :: proc(val: Value, allocator := context.allocator) {
context.allocator = allocator
#partial switch v in val {
case ^Map:
if v == nil { return }
for entry in v {
destroy(entry.key)
destroy(entry.value)
}
delete(v^)
free(v)
case ^Array:
if v == nil { return }
for entry in v {
destroy(entry)
}
delete(v^)
free(v)
case ^Text:
if v == nil { return }
delete(v^)
free(v)
case ^Bytes:
if v == nil { return }
delete(v^)
free(v)
case ^Tag:
if v == nil { return }
destroy(v.value)
free(v)
}
}
/*
to_diagnostic_format either writes or returns a human-readable representation of the value,
optionally formatted, defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]].
Incidentally, if the CBOR does not contain any of the additional types defined on top of JSON
this will also be valid JSON.
*/
to_diagnostic_format :: proc {
to_diagnostic_format_string,
to_diagnostic_format_writer,
}
// Turns the given CBOR value into a human-readable string.
// See docs on the proc group `diagnose` for more info.
to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error {
b := strings.builder_make(allocator)
w := strings.to_stream(&b)
err := to_diagnostic_format_writer(w, val, padding)
if err == .EOF {
// The string builder stream only returns .EOF, and only if it can't write (out of memory).
return "", .Out_Of_Memory
}
assert(err == nil)
return strings.to_string(b), nil
}
// Writes the given CBOR value into the writer as human-readable text.
// See docs on the proc group `diagnose` for more info.
to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error {
@(require_results)
indent :: proc(padding: int) -> int {
padding := padding
if padding != -1 {
padding += 1
}
return padding
}
@(require_results)
dedent :: proc(padding: int) -> int {
padding := padding
if padding != -1 {
padding -= 1
}
return padding
}
comma :: proc(w: io.Writer, padding: int) -> io.Error {
_ = io.write_string(w, ", " if padding == -1 else ",") or_return
return nil
}
newline :: proc(w: io.Writer, padding: int) -> io.Error {
if padding != -1 {
io.write_string(w, "\n") or_return
for _ in 0..<padding {
io.write_string(w, "\t") or_return
}
}
return nil
}
padding := padding
switch v in val {
case u8: io.write_uint(w, uint(v)) or_return
case u16: io.write_uint(w, uint(v)) or_return
case u32: io.write_uint(w, uint(v)) or_return
case u64: io.write_u64(w, v) or_return
case Negative_U8: io.write_int(w, int(negative_to_int(v))) or_return
case Negative_U16: io.write_int(w, int(negative_to_int(v))) or_return
case Negative_U32: io.write_int(w, int(negative_to_int(v))) or_return
case Negative_U64: io.write_i128(w, i128(negative_to_int(v))) or_return
// NOTE: not using io.write_float because it removes the sign,
// which we want for the diagnostic format.
case f16:
buf: [64]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case f32:
buf: [128]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case f64:
buf: [256]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case bool: io.write_string(w, "true" if v else "false") or_return
case Nil: io.write_string(w, "nil") or_return
case Undefined: io.write_string(w, "undefined") or_return
case ^Bytes:
io.write_string(w, "h'") or_return
for b in v { io.write_int(w, int(b), 16) or_return }
io.write_string(w, "'") or_return
case ^Text:
io.write_string(w, `"`) or_return
io.write_string(w, v^) or_return
io.write_string(w, `"`) or_return
case ^Array:
if v == nil || len(v) == 0 {
io.write_string(w, "[]") or_return
return nil
}
io.write_string(w, "[") or_return
padding = indent(padding)
newline(w, padding) or_return
for entry, i in v {
to_diagnostic_format(w, entry, padding) or_return
if i != len(v)-1 {
comma(w, padding) or_return
newline(w, padding) or_return
}
}
padding = dedent(padding)
newline(w, padding) or_return
io.write_string(w, "]") or_return
case ^Map:
if v == nil || len(v) == 0 {
io.write_string(w, "{}") or_return
return nil
}
io.write_string(w, "{") or_return
padding = indent(padding)
newline(w, padding) or_return
for entry, i in v {
to_diagnostic_format(w, entry.key, padding) or_return
io.write_string(w, ": ") or_return
to_diagnostic_format(w, entry.value, padding) or_return
if i != len(v)-1 {
comma(w, padding) or_return
newline(w, padding) or_return
}
}
padding = dedent(padding)
newline(w, padding) or_return
io.write_string(w, "}") or_return
case ^Tag:
io.write_u64(w, v.number) or_return
io.write_string(w, "(") or_return
to_diagnostic_format(w, v.value, padding) or_return
io.write_string(w, ")") or_return
case Simple:
io.write_string(w, "simple(") or_return
io.write_uint(w, uint(v)) or_return
io.write_string(w, ")") or_return
}
return nil
}
/*
Converts from JSON to CBOR.
Everything is copied to the given allocator, the passed in JSON value can be deleted after.
*/
from_json :: proc(val: json.Value, allocator := context.allocator) -> (Value, mem.Allocator_Error) #optional_allocator_error {
internal :: proc(val: json.Value) -> (ret: Value, err: mem.Allocator_Error) {
switch v in val {
case json.Null: return Nil{}, nil
case json.Integer:
i, major := _int_to_uint(v)
#partial switch major {
case .Unsigned: return i, nil
case .Negative: return Negative_U64(i), nil
case: unreachable()
}
case json.Float: return v, nil
case json.Boolean: return v, nil
case json.String:
container := new(Text) or_return
// We need the string to have a nil byte at the end so we clone to cstring.
container^ = string(strings.clone_to_cstring(v) or_return)
return container, nil
case json.Array:
arr := new(Array) or_return
arr^ = make([]Value, len(v)) or_return
for _, i in arr {
arr[i] = internal(v[i]) or_return
}
return arr, nil
case json.Object:
m := new(Map) or_return
dm := make([dynamic]Map_Entry, 0, len(v)) or_return
for mkey, mval in v {
append(&dm, Map_Entry{from_json(mkey) or_return, from_json(mval) or_return})
}
m^ = dm[:]
return m, nil
}
return nil, nil
}
context.allocator = allocator
return internal(val)
}
/*
Converts from CBOR to JSON.
NOTE: overflow on integers or floats is not handled.
Everything is copied to the given allocator, the passed in CBOR value can be `destroy`'ed after.
If a CBOR map with non-string keys is encountered it is turned into an array of tuples.
*/
to_json :: proc(val: Value, allocator := context.allocator) -> (json.Value, mem.Allocator_Error) #optional_allocator_error {
internal :: proc(val: Value) -> (ret: json.Value, err: mem.Allocator_Error) {
switch v in val {
case Simple: return json.Integer(v), nil
case u8: return json.Integer(v), nil
case u16: return json.Integer(v), nil
case u32: return json.Integer(v), nil
case u64: return json.Integer(v), nil
case Negative_U8: return json.Integer(negative_to_int(v)), nil
case Negative_U16: return json.Integer(negative_to_int(v)), nil
case Negative_U32: return json.Integer(negative_to_int(v)), nil
case Negative_U64: return json.Integer(negative_to_int(v)), nil
case f16: return json.Float(v), nil
case f32: return json.Float(v), nil
case f64: return json.Float(v), nil
case bool: return json.Boolean(v), nil
case Undefined: return json.Null{}, nil
case Nil: return json.Null{}, nil
case ^Bytes: return json.String(strings.clone(string(v^)) or_return), nil
case ^Text: return json.String(strings.clone(v^) or_return), nil
case ^Map:
keys_all_strings :: proc(m: ^Map) -> bool {
for entry in m {
#partial switch kv in entry.key {
case ^Bytes:
case ^Text:
case: return false
}
}
return false
}
if keys_all_strings(v) {
obj := make(json.Object, len(v)) or_return
for entry in v {
k: string
#partial switch kv in entry.key {
case ^Bytes: k = string(kv^)
case ^Text: k = kv^
case: unreachable()
}
v := internal(entry.value) or_return
obj[k] = v
}
return obj, nil
} else {
// Resort to an array of tuples if keys aren't all strings.
arr := make(json.Array, 0, len(v)) or_return
for entry in v {
entry_arr := make(json.Array, 0, 2) or_return
append(&entry_arr, internal(entry.key) or_return) or_return
append(&entry_arr, internal(entry.value) or_return) or_return
append(&arr, entry_arr) or_return
}
return arr, nil
}
case ^Array:
arr := make(json.Array, 0, len(v)) or_return
for entry in v {
append(&arr, internal(entry) or_return) or_return
}
return arr, nil
case ^Tag:
obj := make(json.Object, 2) or_return
obj[strings.clone("number") or_return] = internal(v.number) or_return
obj[strings.clone("value") or_return] = internal(v.value) or_return
return obj, nil
case: return json.Null{}, nil
}
}
context.allocator = allocator
return internal(val)
}
_int_to_uint :: proc {
_i8_to_uint,
_i16_to_uint,
_i32_to_uint,
_i64_to_uint,
_i128_to_uint,
}
_u128_to_u64 :: #force_inline proc(v: u128) -> (u64, Encode_Data_Error) {
if v > u128(max(u64)) {
return 0, .Int_Too_Big
}
return u64(v), nil
}
_i8_to_uint :: #force_inline proc(v: i8) -> (u: u8, m: Major) {
if v < 0 {
return u8(abs(v)-1), .Negative
}
return u8(v), .Unsigned
}
_i16_to_uint :: #force_inline proc(v: i16) -> (u: u16, m: Major) {
if v < 0 {
return u16(abs(v)-1), .Negative
}
return u16(v), .Unsigned
}
_i32_to_uint :: #force_inline proc(v: i32) -> (u: u32, m: Major) {
if v < 0 {
return u32(abs(v)-1), .Negative
}
return u32(v), .Unsigned
}
_i64_to_uint :: #force_inline proc(v: i64) -> (u: u64, m: Major) {
if v < 0 {
return u64(abs(v)-1), .Negative
}
return u64(v), .Unsigned
}
_i128_to_uint :: proc(v: i128) -> (u: u64, m: Major, err: Encode_Data_Error) {
if v < 0 {
m = .Negative
u, err = _u128_to_u64(u128(abs(v) - 1))
return
}
m = .Unsigned
u, err = _u128_to_u64(u128(v))
return
}
+886
View File
@@ -0,0 +1,886 @@
package encoding_cbor
import "base:intrinsics"
import "base:runtime"
import "core:bytes"
import "core:encoding/endian"
import "core:io"
import "core:slice"
import "core:strings"
Encoder_Flag :: enum {
// CBOR defines a tag header that also acts as a file/binary header,
// this way decoders can check the first header of the binary and see if it is CBOR.
Self_Described_CBOR,
// Integers are stored in the smallest integer type it fits.
// This involves checking each int against the max of all its smaller types.
Deterministic_Int_Size,
// Floats are stored in the smallest size float type without losing precision.
// This involves casting each float down to its smaller types and checking if it changed.
Deterministic_Float_Size,
// Sort maps by their keys in bytewise lexicographic order of their deterministic encoding.
// NOTE: In order to do this, all keys of a map have to be pre-computed, sorted, and
// then written, this involves temporary allocations for the keys and a copy of the map itself.
Deterministic_Map_Sorting,
}
Encoder_Flags :: bit_set[Encoder_Flag]
// Flags for fully deterministic output (if you are not using streaming/indeterminate length).
ENCODE_FULLY_DETERMINISTIC :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size, .Deterministic_Map_Sorting}
// Flags for the smallest encoding output.
ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size}
Encoder :: struct {
flags: Encoder_Flags,
writer: io.Writer,
temp_allocator: runtime.Allocator,
}
Decoder_Flag :: enum {
// Rejects (with an error `.Disallowed_Streaming`) when a streaming CBOR header is encountered.
Disallow_Streaming,
// Pre-allocates buffers and containers with the size that was set in the CBOR header.
// This should only be enabled when you control both ends of the encoding, if you don't,
// attackers can craft input that causes massive (`max(u64)`) byte allocations for a few bytes of
// CBOR.
Trusted_Input,
// Makes the decoder shrink of excess capacity from allocated buffers/containers before returning.
Shrink_Excess,
}
Decoder_Flags :: bit_set[Decoder_Flag]
Decoder :: struct {
// The max amount of bytes allowed to pre-allocate when `.Trusted_Input` is not set on the
// flags.
max_pre_alloc: int,
flags: Decoder_Flags,
reader: io.Reader,
}
/*
Decodes both deterministic and non-deterministic CBOR into a `Value` variant.
`Text` and `Bytes` can safely be cast to cstrings because of an added 0 byte.
Allocations are done using the given allocator,
*no* allocations are done on the `context.temp_allocator`.
A value can be (fully and recursively) deallocated using the `destroy` proc in this package.
Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag.
Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag.
Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature
of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only
do this when you own both sides of the encoding and are sure there can't be malicious bytes used as
an input.
*/
decode_from :: proc {
decode_from_string,
decode_from_reader,
decode_from_decoder,
}
decode :: decode_from
// Decodes the given string as CBOR.
// See docs on the proc group `decode` for more information.
decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
r: strings.Reader
strings.reader_init(&r, s)
return decode_from_reader(strings.reader_to_stream(&r), flags, allocator)
}
// Reads a CBOR value from the given reader.
// See docs on the proc group `decode` for more information.
decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
return decode_from_decoder(
Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
allocator=allocator,
)
}
// Reads a CBOR value from the given decoder.
// See docs on the proc group `decode` for more information.
decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
context.allocator = allocator
d := d
if d.max_pre_alloc <= 0 {
d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
}
v, err = _decode_from_decoder(d)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) {
hdr := hdr
r := d.reader
if hdr == Header(0) { hdr = _decode_header(r) or_return }
switch hdr {
case .U8: return _decode_u8 (r)
case .U16: return _decode_u16(r)
case .U32: return _decode_u32(r)
case .U64: return _decode_u64(r)
case .Neg_U8: return Negative_U8 (_decode_u8 (r) or_return), nil
case .Neg_U16: return Negative_U16(_decode_u16(r) or_return), nil
case .Neg_U32: return Negative_U32(_decode_u32(r) or_return), nil
case .Neg_U64: return Negative_U64(_decode_u64(r) or_return), nil
case .Simple: return _decode_simple(r)
case .F16: return _decode_f16(r)
case .F32: return _decode_f32(r)
case .F64: return _decode_f64(r)
case .True: return true, nil
case .False: return false, nil
case .Nil: return Nil{}, nil
case .Undefined: return Undefined{}, nil
case .Break: return nil, .Break
}
maj, add := _header_split(hdr)
switch maj {
case .Unsigned: return _decode_tiny_u8(add)
case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
case .Bytes: return _decode_bytes_ptr(d, add)
case .Text: return _decode_text_ptr(d, add)
case .Array: return _decode_array_ptr(d, add)
case .Map: return _decode_map_ptr(d, add)
case .Tag: return _decode_tag_ptr(d, add)
case .Other: return _decode_tiny_simple(add)
case: return nil, .Bad_Major
}
}
/*
Encodes the CBOR value into a binary CBOR.
Flags can be used to control the output (mainly determinism, which coincidently affects size).
The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try
to put ints and floats into their smallest possible byte size without losing equality.
Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know
the contents are CBOR from just reading the first byte.
Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the
encoded key. This flag has a cost on performance and memory efficiency because all keys in a map
have to be precomputed, sorted and only then written to the output.
Empty flags will do nothing extra to the value.
The allocations for the `.Deterministic_Map_Sorting` flag are done using the given temp_allocator.
but are followed by the necessary `delete` and `free` calls if the allocator supports them.
This is helpful when the CBOR size is so big that you don't want to collect all the temporary
allocations until the end.
*/
encode_into :: proc {
encode_into_bytes,
encode_into_builder,
encode_into_writer,
encode_into_encoder,
}
encode :: encode_into
// Encodes the CBOR value into binary CBOR allocated on the given allocator.
// See the docs on the proc group `encode_into` for more info.
encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) {
b := strings.builder_make(allocator) or_return
encode_into_builder(&b, v, flags, temp_allocator) or_return
return b.buf[:], nil
}
// Encodes the CBOR value into binary CBOR written to the given builder.
// See the docs on the proc group `encode_into` for more info.
encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator)
}
// Encodes the CBOR value into binary CBOR written to the given writer.
// See the docs on the proc group `encode_into` for more info.
encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
return encode_into_encoder(Encoder{flags, w, temp_allocator}, v)
}
// Encodes the CBOR value into binary CBOR written to the given encoder.
// See the docs on the proc group `encode_into` for more info.
encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
e := e
if e.temp_allocator.procedure == nil {
e.temp_allocator = context.temp_allocator
}
if .Self_Described_CBOR in e.flags {
_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return
e.flags &~= { .Self_Described_CBOR }
}
switch v_spec in v {
case u8: return _encode_u8(e.writer, v_spec, .Unsigned)
case u16: return _encode_u16(e, v_spec, .Unsigned)
case u32: return _encode_u32(e, v_spec, .Unsigned)
case u64: return _encode_u64(e, v_spec, .Unsigned)
case Negative_U8: return _encode_u8(e.writer, u8(v_spec), .Negative)
case Negative_U16: return _encode_u16(e, u16(v_spec), .Negative)
case Negative_U32: return _encode_u32(e, u32(v_spec), .Negative)
case Negative_U64: return _encode_u64(e, u64(v_spec), .Negative)
case ^Bytes: return _encode_bytes(e, v_spec^)
case ^Text: return _encode_text(e, v_spec^)
case ^Array: return _encode_array(e, v_spec^)
case ^Map: return _encode_map(e, v_spec^)
case ^Tag: return _encode_tag(e, v_spec^)
case Simple: return _encode_simple(e.writer, v_spec)
case f16: return _encode_f16(e.writer, v_spec)
case f32: return _encode_f32(e, v_spec)
case f64: return _encode_f64(e, v_spec)
case bool: return _encode_bool(e.writer, v_spec)
case Nil: return _encode_nil(e.writer)
case Undefined: return _encode_undefined(e.writer)
case: return nil
}
}
_decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) {
hdr = Header(_decode_u8(r) or_return)
return
}
_header_split :: proc(hdr: Header) -> (Major, Add) {
return Major(u8(hdr) >> 5), Add(u8(hdr) & 0x1f)
}
_decode_u8 :: proc(r: io.Reader) -> (v: u8, err: io.Error) {
byte: [1]byte = ---
io.read_full(r, byte[:]) or_return
return byte[0], nil
}
_encode_uint :: proc {
_encode_u8,
_encode_u16,
_encode_u32,
_encode_u64,
}
_encode_u8 :: proc(w: io.Writer, v: u8, major: Major = .Unsigned) -> (err: io.Error) {
header := u8(major) << 5
if v < u8(Add.One_Byte) {
header |= v
_, err = io.write_full(w, {header})
return
}
header |= u8(Add.One_Byte)
_, err = io.write_full(w, {header, v})
return
}
_decode_tiny_u8 :: proc(additional: Add) -> (u8, Decode_Data_Error) {
if additional < .One_Byte {
return u8(additional), nil
}
return 0, .Bad_Argument
}
_decode_u16 :: proc(r: io.Reader) -> (v: u16, err: io.Error) {
bytes: [2]byte = ---
io.read_full(r, bytes[:]) or_return
return endian.unchecked_get_u16be(bytes[:]), nil
}
_encode_u16 :: proc(e: Encoder, v: u16, major: Major = .Unsigned) -> Encode_Error {
if .Deterministic_Int_Size in e.flags {
return _encode_deterministic_uint(e.writer, v, major)
}
return _encode_u16_exact(e.writer, v, major)
}
_encode_u16_exact :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> (err: io.Error) {
bytes: [3]byte = ---
bytes[0] = (u8(major) << 5) | u8(Add.Two_Bytes)
endian.unchecked_put_u16be(bytes[1:], v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_u32 :: proc(r: io.Reader) -> (v: u32, err: io.Error) {
bytes: [4]byte = ---
io.read_full(r, bytes[:]) or_return
return endian.unchecked_get_u32be(bytes[:]), nil
}
_encode_u32 :: proc(e: Encoder, v: u32, major: Major = .Unsigned) -> Encode_Error {
if .Deterministic_Int_Size in e.flags {
return _encode_deterministic_uint(e.writer, v, major)
}
return _encode_u32_exact(e.writer, v, major)
}
_encode_u32_exact :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> (err: io.Error) {
bytes: [5]byte = ---
bytes[0] = (u8(major) << 5) | u8(Add.Four_Bytes)
endian.unchecked_put_u32be(bytes[1:], v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_u64 :: proc(r: io.Reader) -> (v: u64, err: io.Error) {
bytes: [8]byte = ---
io.read_full(r, bytes[:]) or_return
return endian.unchecked_get_u64be(bytes[:]), nil
}
_encode_u64 :: proc(e: Encoder, v: u64, major: Major = .Unsigned) -> Encode_Error {
if .Deterministic_Int_Size in e.flags {
return _encode_deterministic_uint(e.writer, v, major)
}
return _encode_u64_exact(e.writer, v, major)
}
_encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (err: io.Error) {
bytes: [9]byte = ---
bytes[0] = (u8(major) << 5) | u8(Add.Eight_Bytes)
endian.unchecked_put_u64be(bytes[1:], v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) {
v = new(Bytes) or_return
defer if err != nil { free(v) }
v^ = _decode_bytes(d, add, type) or_return
return
}
_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) {
context.allocator = allocator
add := add
n, scap := _decode_len_str(d, add) or_return
buf := strings.builder_make(0, scap) or_return
defer if err != nil { strings.builder_destroy(&buf) }
buf_stream := strings.to_stream(&buf)
if n == -1 {
indefinite_loop: for {
header := _decode_header(d.reader) or_return
maj: Major
maj, add = _header_split(header)
#partial switch maj {
case type:
iter_n, iter_cap := _decode_len_str(d, add) or_return
if iter_n == -1 {
return nil, .Nested_Indefinite_Length
}
reserve(&buf.buf, len(buf.buf) + iter_cap) or_return
io.copy_n(buf_stream, d.reader, i64(iter_n)) or_return
case .Other:
if add != .Break { return nil, .Bad_Argument }
break indefinite_loop
case:
return nil, .Bad_Major
}
}
} else {
io.copy_n(buf_stream, d.reader, i64(n)) or_return
}
v = buf.buf[:]
// Write zero byte so this can be converted to cstring.
strings.write_byte(&buf, 0)
if .Shrink_Excess in d.flags { shrink(&buf.buf) }
return
}
_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) {
assert(len(val) >= 0)
_encode_u64(e, u64(len(val)), major) or_return
_, err = io.write_full(e.writer, val[:])
return
}
_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) {
v = new(Text) or_return
defer if err != nil { free(v) }
v^ = _decode_text(d, add) or_return
return
}
_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) {
return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil
}
_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
return _encode_bytes(e, transmute([]byte)val, .Text)
}
_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) {
v = new(Array) or_return
defer if err != nil { free(v) }
v^ = _decode_array(d, add) or_return
return
}
_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
n, scap := _decode_len_container(d, add) or_return
array := make([dynamic]Value, 0, scap) or_return
defer if err != nil {
for entry in array { destroy(entry) }
delete(array)
}
for i := 0; n == -1 || i < n; i += 1 {
val, verr := _decode_from_decoder(d)
if n == -1 && verr == .Break {
break
} else if verr != nil {
err = verr
return
}
append(&array, val) or_return
}
if .Shrink_Excess in d.flags { shrink(&array) }
v = array[:]
return
}
_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
assert(len(arr) >= 0)
_encode_u64(e, u64(len(arr)), .Array)
for val in arr {
encode(e, val) or_return
}
return nil
}
_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) {
v = new(Map) or_return
defer if err != nil { free(v) }
v^ = _decode_map(d, add) or_return
return
}
_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) {
n, scap := _decode_len_container(d, add) or_return
items := make([dynamic]Map_Entry, 0, scap) or_return
defer if err != nil {
for entry in items {
destroy(entry.key)
destroy(entry.value)
}
delete(items)
}
for i := 0; n == -1 || i < n; i += 1 {
key, kerr := _decode_from_decoder(d)
if n == -1 && kerr == .Break {
break
} else if kerr != nil {
return nil, kerr
}
value := _decode_from_decoder(d) or_return
append(&items, Map_Entry{
key = key,
value = value,
}) or_return
}
if .Shrink_Excess in d.flags { shrink(&items) }
v = items[:]
return
}
_encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
assert(len(m) >= 0)
_encode_u64(e, u64(len(m)), .Map) or_return
if .Deterministic_Map_Sorting not_in e.flags {
for entry in m {
encode(e, entry.key) or_return
encode(e, entry.value) or_return
}
return
}
// Deterministic_Map_Sorting needs us to sort the entries by the byte contents of the
// encoded key.
//
// This means we have to store and sort them before writing incurring extra (temporary) allocations.
Map_Entry_With_Key :: struct {
encoded_key: []byte,
entry: Map_Entry,
}
entries := make([]Map_Entry_With_Key, len(m), e.temp_allocator) or_return
defer delete(entries, e.temp_allocator)
for &entry, i in entries {
entry.entry = m[i]
buf := strings.builder_make(e.temp_allocator) or_return
ke := e
ke.writer = strings.to_stream(&buf)
encode(ke, entry.entry.key) or_return
entry.encoded_key = buf.buf[:]
}
// Sort lexicographic on the bytes of the key.
slice.sort_by_cmp(entries, proc(a, b: Map_Entry_With_Key) -> slice.Ordering {
return slice.Ordering(bytes.compare(a.encoded_key, b.encoded_key))
})
for entry in entries {
io.write_full(e.writer, entry.encoded_key) or_return
delete(entry.encoded_key, e.temp_allocator)
encode(e, entry.entry.value) or_return
}
return nil
}
_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) {
tag := _decode_tag(d, add) or_return
if t, ok := tag.?; ok {
defer if err != nil { destroy(t.value) }
tp := new(Tag) or_return
tp^ = t
return tp, nil
}
// no error, no tag, this was the self described CBOR tag, skip it.
return _decode_from_decoder(d)
}
_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) {
num := _decode_uint_as_u64(d.reader, add) or_return
// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
// We can ignore it here.
if num == TAG_SELF_DESCRIBED_CBOR {
return
}
t := Tag{
number = num,
value = _decode_from_decoder(d) or_return,
}
if nested, ok := t.value.(^Tag); ok {
destroy(nested)
return nil, .Nested_Tag
}
return t, nil
}
_decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Error) {
#partial switch add {
case .One_Byte: return u64(_decode_u8(r) or_return), nil
case .Two_Bytes: return u64(_decode_u16(r) or_return), nil
case .Four_Bytes: return u64(_decode_u32(r) or_return), nil
case .Eight_Bytes: return u64(_decode_u64(r) or_return), nil
case: return u64(_decode_tiny_u8(add) or_return), nil
}
}
_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error {
_encode_u64(e, val.number, .Tag) or_return
return encode(e, val.value)
}
_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) {
buf: [1]byte = ---
io.read_full(r, buf[:]) or_return
return Simple(buf[0]), nil
}
_encode_simple :: proc(w: io.Writer, v: Simple) -> (err: Encode_Error) {
header := u8(Major.Other) << 5
if v < Simple(Add.False) {
header |= u8(v)
_, err = io.write_full(w, {header})
return
} else if v <= Simple(Add.Break) {
return .Invalid_Simple
}
header |= u8(Add.One_Byte)
_, err = io.write_full(w, {header, u8(v)})
return
}
_decode_tiny_simple :: proc(add: Add) -> (Simple, Decode_Data_Error) {
if add < Add.False {
return Simple(add), nil
}
return 0, .Bad_Argument
}
_decode_f16 :: proc(r: io.Reader) -> (v: f16, err: io.Error) {
bytes: [2]byte = ---
io.read_full(r, bytes[:]) or_return
n := endian.unchecked_get_u16be(bytes[:])
return transmute(f16)n, nil
}
_encode_f16 :: proc(w: io.Writer, v: f16) -> (err: io.Error) {
bytes: [3]byte = ---
bytes[0] = u8(Header.F16)
endian.unchecked_put_u16be(bytes[1:], transmute(u16)v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_f32 :: proc(r: io.Reader) -> (v: f32, err: io.Error) {
bytes: [4]byte = ---
io.read_full(r, bytes[:]) or_return
n := endian.unchecked_get_u32be(bytes[:])
return transmute(f32)n, nil
}
_encode_f32 :: proc(e: Encoder, v: f32) -> io.Error {
if .Deterministic_Float_Size in e.flags {
return _encode_deterministic_float(e.writer, v)
}
return _encode_f32_exact(e.writer, v)
}
_encode_f32_exact :: proc(w: io.Writer, v: f32) -> (err: io.Error) {
bytes: [5]byte = ---
bytes[0] = u8(Header.F32)
endian.unchecked_put_u32be(bytes[1:], transmute(u32)v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_f64 :: proc(r: io.Reader) -> (v: f64, err: io.Error) {
bytes: [8]byte = ---
io.read_full(r, bytes[:]) or_return
n := endian.unchecked_get_u64be(bytes[:])
return transmute(f64)n, nil
}
_encode_f64 :: proc(e: Encoder, v: f64) -> io.Error {
if .Deterministic_Float_Size in e.flags {
return _encode_deterministic_float(e.writer, v)
}
return _encode_f64_exact(e.writer, v)
}
_encode_f64_exact :: proc(w: io.Writer, v: f64) -> (err: io.Error) {
bytes: [9]byte = ---
bytes[0] = u8(Header.F64)
endian.unchecked_put_u64be(bytes[1:], transmute(u64)v)
_, err = io.write_full(w, bytes[:])
return
}
_encode_bool :: proc(w: io.Writer, v: bool) -> (err: io.Error) {
switch v {
case true: _, err = io.write_full(w, {u8(Header.True )}); return
case false: _, err = io.write_full(w, {u8(Header.False)}); return
case: unreachable()
}
}
_encode_undefined :: proc(w: io.Writer) -> io.Error {
_, err := io.write_full(w, {u8(Header.Undefined)})
return err
}
_encode_nil :: proc(w: io.Writer) -> io.Error {
_, err := io.write_full(w, {u8(Header.Nil)})
return err
}
// Streaming
encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) {
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
header := (u8(major) << 5) | u8(Add.Length_Unknown)
_, err = io.write_full(w, {header})
return
}
encode_stream_end :: proc(w: io.Writer) -> io.Error {
header := (u8(Major.Other) << 5) | u8(Add.Break)
_, err := io.write_full(w, {header})
return err
}
encode_stream_bytes :: _encode_bytes
encode_stream_text :: _encode_text
encode_stream_array_item :: encode
encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error {
encode(e, key) or_return
return encode(e, val)
}
// For `Bytes` and `Text` strings: Decodes the number of items the header says follows.
// If the number is not specified -1 is returned and streaming should be initiated.
// A suitable starting capacity is also returned for a buffer that is allocated up the stack.
_decode_len_str :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) {
if add == .Length_Unknown {
if .Disallow_Streaming in d.flags {
return -1, -1, .Disallowed_Streaming
}
return -1, INITIAL_STREAMED_BYTES_CAPACITY, nil
}
_n := _decode_uint_as_u64(d.reader, add) or_return
if _n > u64(max(int)) { return -1, -1, .Length_Too_Big }
n = int(_n)
scap = n + 1 // Space for zero byte.
if .Trusted_Input not_in d.flags {
scap = min(d.max_pre_alloc, scap)
}
return
}
// For `Array` and `Map` types: Decodes the number of items the header says follows.
// If the number is not specified -1 is returned and streaming should be initiated.
// A suitable starting capacity is also returned for a buffer that is allocated up the stack.
_decode_len_container :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) {
if add == .Length_Unknown {
if .Disallow_Streaming in d.flags {
return -1, -1, .Disallowed_Streaming
}
return -1, INITIAL_STREAMED_CONTAINER_CAPACITY, nil
}
_n := _decode_uint_as_u64(d.reader, add) or_return
if _n > u64(max(int)) { return -1, -1, .Length_Too_Big }
n = int(_n)
scap = n
if .Trusted_Input not_in d.flags {
// NOTE: if this is a map it will be twice this.
scap = min(d.max_pre_alloc / size_of(Value), scap)
}
return
}
// Deterministic encoding is (among other things) encoding all values into their smallest
// possible representation.
// See section 4 of RFC 8949.
_encode_deterministic_uint :: proc {
_encode_u8,
_encode_deterministic_u16,
_encode_deterministic_u32,
_encode_deterministic_u64,
_encode_deterministic_u128,
}
_encode_deterministic_u16 :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u16(max(u8)): return _encode_u8(w, u8(v), major)
case: return _encode_u16_exact(w, v, major)
}
}
_encode_deterministic_u32 :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u32(max(u8)): return _encode_u8(w, u8(v), major)
case v <= u32(max(u16)): return _encode_u16_exact(w, u16(v), major)
case: return _encode_u32_exact(w, u32(v), major)
}
}
_encode_deterministic_u64 :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u64(max(u8)): return _encode_u8(w, u8(v), major)
case v <= u64(max(u16)): return _encode_u16_exact(w, u16(v), major)
case v <= u64(max(u32)): return _encode_u32_exact(w, u32(v), major)
case: return _encode_u64_exact(w, u64(v), major)
}
}
_encode_deterministic_u128 :: proc(w: io.Writer, v: u128, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u128(max(u8)): return _encode_u8(w, u8(v), major)
case v <= u128(max(u16)): return _encode_u16_exact(w, u16(v), major)
case v <= u128(max(u32)): return _encode_u32_exact(w, u32(v), major)
case v <= u128(max(u64)): return _encode_u64_exact(w, u64(v), major)
case: return .Int_Too_Big
}
}
_encode_deterministic_negative :: #force_inline proc(w: io.Writer, v: $T) -> Encode_Error
where T == Negative_U8 || T == Negative_U16 || T == Negative_U32 || T == Negative_U64 {
return _encode_deterministic_uint(w, v, .Negative)
}
// A Deterministic float is a float in the smallest type that stays the same after down casting.
_encode_deterministic_float :: proc {
_encode_f16,
_encode_deterministic_f32,
_encode_deterministic_f64,
}
_encode_deterministic_f32 :: proc(w: io.Writer, v: f32) -> io.Error {
if (f32(f16(v)) == v) {
return _encode_f16(w, f16(v))
}
return _encode_f32_exact(w, v)
}
_encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
if (f64(f16(v)) == v) {
return _encode_f16(w, f16(v))
}
if (f64(f32(v)) == v) {
return _encode_f32_exact(w, f32(v))
}
return _encode_f64_exact(w, v)
}
+141
View File
@@ -0,0 +1,141 @@
/*
Package cbor encodes, decodes, marshals and unmarshals types from/into RCF 8949 compatible CBOR binary.
Also provided are conversion to and from JSON and the CBOR diagnostic format.
**Allocations:**
In general, when in the following table it says allocations are done on the `temp_allocator`, these allocations
are still attempted to be deallocated.
This allows you to use an allocator with freeing implemented as the `temp_allocator` which is handy with big CBOR.
- *Encoding*: If the `.Deterministic_Map_Sorting` flag is set on the encoder, this allocates on the given `temp_allocator`
some space for the keys of maps in order to sort them and then write them.
Other than that there are no allocations (only for the final bytes if you use `cbor.encode_into_bytes`.
- *Decoding*: Allocates everything on the given allocator and input given can be deleted after decoding.
*No* temporary allocations are done.
- *Marshal*: Same allocation strategy as encoding.
- *Unmarshal*: Allocates everything on the given allocator and input given can be deleted after unmarshalling.
Some temporary allocations are done on the given `temp_allocator`.
**Determinism:**
CBOR defines a deterministic en/decoder, which among other things uses the smallest type possible for integers and floats,
and sorts map keys by their (encoded) lexical bytewise order.
You can enable this behaviour using a combination of flags, also available as the `cbor.ENCODE_FULLY_DETERMINISTIC` constant.
If you just want the small size that comes with this, but not the map sorting (which has a performance cost) you can use the
`cbor.ENCODE_SMALL` constant for the flags.
A deterministic float is a float in the smallest type (f16, f32, f64) that hasn't changed after conversion.
A deterministic integer is an integer in the smallest representation (u8, u16, u32, u64) it fits in.
**Untrusted Input:**
By default input is treated as untrusted, this means the sizes that are encoded in the CBOR are not blindly trusted.
If you were to trust these sizes, and allocate space for them an attacker would be able to cause massive allocations with small payloads.
The decoder has a `max_pre_alloc` field that specifies the maximum amount of bytes (roughly) to pre allocate, a KiB by default.
This does mean reallocations are more common though, you can, if you know the input is trusted, add the `.Trusted_Input` flag to the decoder.
**Tags:**
CBOR describes tags that you can wrap values with to assign a number to describe what type of data will follow.
More information and a list of default tags can be found here: [[RFC 8949 Section 3.4;https://www.rfc-editor.org/rfc/rfc8949.html#name-tagging-of-items]].
A list of registered extension types can be found here: [[IANA CBOR assignments;https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml]].
Tags can either be assigned to a distinct Odin type (used by default),
or be used with struct tags (`cbor_tag:"base64"`, or `cbor_tag:"1"` for example).
By default, the following tags are supported/provided by this implementation:
- *1/epoch*: Assign this tag to `time.Time` or integer fields to use the defined seconds since epoch format.
- *24/cbor*: Assign this tag to string or byte fields to store encoded CBOR (not decoding it).
- *34/base64*: Assign this tag to string or byte fields to store and decode the contents in base64.
- *2 & 3*: Used automatically by the implementation to encode and decode big numbers into/from `core:math/big`.
- *55799*: Self described CBOR, used when `.Self_Described_CBOR` flag is used to wrap the entire binary.
This shows other implementations that we are dealing with CBOR by just looking at the first byte of input.
- *1010*: An extension tag that defines a string type followed by its value, this is used by this implementation to support Odin's unions.
Users can provide their own tag implementations using the `cbor.tag_register_type(...)` to register a tag for a distinct Odin type
used automatically when it is encountered during marshal and unmarshal.
Or with `cbor.tag_register_number(...)` to register a tag number along with an identifier for convenience that can be used with struct tags,
e.g. `cbor_tag:"69"` or `cbor_tag:"my_tag"`.
You can look at the default tags provided for pointers on how these implementations work.
Example:
package main
import "core:encoding/cbor"
import "core:fmt"
import "core:time"
Possibilities :: union {
string,
int,
}
Data :: struct {
str: string,
neg: cbor.Negative_U16, // Store a CBOR value directly.
now: time.Time `cbor_tag:"epoch"`, // Wrapped in the epoch tag.
ignore_this: ^Data `cbor:"-"`, // Ignored by implementation.
renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded.
my_union: Possibilities, // Union support.
}
main :: proc() {
now := time.Time{_nsec = 1701117968 * 1e9}
data := Data{
str = "Hello, World!",
neg = 300,
now = now,
ignore_this = &Data{},
renamed = 123123.125,
my_union = 3,
}
// Marshal the struct into binary CBOR.
binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC)
assert(err == nil)
defer delete(binary)
// Decode the binary data into a `cbor.Value`.
decoded, derr := cbor.decode(string(binary))
assert(derr == nil)
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)
defer delete(diagnosis)
fmt.println(diagnosis)
}
Output:
{
"my_union": 1010([
"int",
3
]),
"neg": -301,
"now": 1(1701117968),
"renamed :)": 123123.12500000,
"str": "Hello, World!"
}
*/
package encoding_cbor
+575
View File
@@ -0,0 +1,575 @@
package encoding_cbor
import "base:intrinsics"
import "base:runtime"
import "core:bytes"
import "core:io"
import "core:mem"
import "core:reflect"
import "core:slice"
import "core:strconv"
import "core:strings"
import "core:unicode/utf8"
/*
Marshal a value into binary CBOR.
Flags can be used to control the output (mainly determinism, which coincidently affects size).
The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try
to put ints and floats into their smallest possible byte size without losing equality.
Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know
the contents are CBOR from just reading the first byte.
Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the
encoded key. This flag has a cost on performance and memory efficiency because all keys in a map
have to be precomputed, sorted and only then written to the output.
Empty flags will do nothing extra to the value.
The allocations for the `.Deterministic_Map_Sorting` flag are done using the given `temp_allocator`.
but are followed by the necessary `delete` and `free` calls if the allocator supports them.
This is helpful when the CBOR size is so big that you don't want to collect all the temporary
allocations until the end.
*/
marshal_into :: proc {
marshal_into_bytes,
marshal_into_builder,
marshal_into_writer,
marshal_into_encoder,
}
marshal :: marshal_into
// Marshals the given value into a CBOR byte stream (allocated using the given allocator).
// See docs on the `marshal_into` proc group for more info.
marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) {
b, alloc_err := strings.builder_make(allocator)
// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
if alloc_err != nil {
return nil, .EOF
}
defer if err != nil { strings.builder_destroy(&b) }
if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
return
}
return b.buf[:], nil
}
// 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) -> 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) -> Marshal_Error {
encoder := Encoder{flags, w, temp_allocator}
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) -> (err: Marshal_Error) {
e := e
if e.temp_allocator.procedure == nil {
e.temp_allocator = context.temp_allocator
}
if .Self_Described_CBOR in e.flags {
err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return
e.flags &~= { .Self_Described_CBOR }
}
if v == nil {
return _encode_nil(e.writer)
}
// Check if type has a tag implementation to use.
if impl, ok := _tag_implementations_type[v.id]; ok {
return impl->marshal(e, v)
}
ti := runtime.type_info_base(type_info_of(v.id))
a := any{v.data, ti.id}
#partial switch info in ti.variant {
case runtime.Type_Info_Named:
unreachable()
case runtime.Type_Info_Pointer:
switch vv in v {
case Undefined: return _encode_undefined(e.writer)
case Nil: return _encode_nil(e.writer)
}
case runtime.Type_Info_Integer:
switch vv in v {
case Simple: return err_conv(_encode_simple(e.writer, vv))
case Negative_U8: return _encode_u8(e.writer, u8(vv), .Negative)
case Negative_U16: return err_conv(_encode_u16(e, u16(vv), .Negative))
case Negative_U32: return err_conv(_encode_u32(e, u32(vv), .Negative))
case Negative_U64: return err_conv(_encode_u64(e, u64(vv), .Negative))
}
switch i in a {
case i8: return _encode_uint(e.writer, _int_to_uint(i))
case i16: return err_conv(_encode_uint(e, _int_to_uint(i)))
case i32: return err_conv(_encode_uint(e, _int_to_uint(i)))
case i64: return err_conv(_encode_uint(e, _int_to_uint(i)))
case i128: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return))
case int: return err_conv(_encode_uint(e, _int_to_uint(i64(i))))
case u8: return _encode_uint(e.writer, i)
case u16: return err_conv(_encode_uint(e, i))
case u32: return err_conv(_encode_uint(e, i))
case u64: return err_conv(_encode_uint(e, i))
case u128: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return))
case uint: return err_conv(_encode_uint(e, u64(i)))
case uintptr: return err_conv(_encode_uint(e, u64(i)))
case i16le: return err_conv(_encode_uint(e, _int_to_uint(i16(i))))
case i32le: return err_conv(_encode_uint(e, _int_to_uint(i32(i))))
case i64le: return err_conv(_encode_uint(e, _int_to_uint(i64(i))))
case i128le: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return))
case u16le: return err_conv(_encode_uint(e, u16(i)))
case u32le: return err_conv(_encode_uint(e, u32(i)))
case u64le: return err_conv(_encode_uint(e, u64(i)))
case u128le: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return))
case i16be: return err_conv(_encode_uint(e, _int_to_uint(i16(i))))
case i32be: return err_conv(_encode_uint(e, _int_to_uint(i32(i))))
case i64be: return err_conv(_encode_uint(e, _int_to_uint(i64(i))))
case i128be: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return))
case u16be: return err_conv(_encode_uint(e, u16(i)))
case u32be: return err_conv(_encode_uint(e, u32(i)))
case u64be: return err_conv(_encode_uint(e, u64(i)))
case u128be: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return))
}
case runtime.Type_Info_Rune:
buf, w := utf8.encode_rune(a.(rune))
return err_conv(_encode_text(e, string(buf[:w])))
case runtime.Type_Info_Float:
switch f in a {
case f16: return _encode_f16(e.writer, f)
case f32: return _encode_f32(e, f)
case f64: return _encode_f64(e, f)
case f16le: return _encode_f16(e.writer, f16(f))
case f32le: return _encode_f32(e, f32(f))
case f64le: return _encode_f64(e, f64(f))
case f16be: return _encode_f16(e.writer, f16(f))
case f32be: return _encode_f32(e, f32(f))
case f64be: return _encode_f64(e, f64(f))
}
case runtime.Type_Info_Complex:
switch z in a {
case complex32:
arr: [2]Value = {real(z), imag(z)}
return err_conv(_encode_array(e, arr[:]))
case complex64:
arr: [2]Value = {real(z), imag(z)}
return err_conv(_encode_array(e, arr[:]))
case complex128:
arr: [2]Value = {real(z), imag(z)}
return err_conv(_encode_array(e, arr[:]))
}
case runtime.Type_Info_Quaternion:
switch q in a {
case quaternion64:
arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)}
return err_conv(_encode_array(e, arr[:]))
case quaternion128:
arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)}
return err_conv(_encode_array(e, arr[:]))
case quaternion256:
arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)}
return err_conv(_encode_array(e, arr[:]))
}
case runtime.Type_Info_String:
switch s in a {
case string: return err_conv(_encode_text(e, s))
case cstring: return err_conv(_encode_text(e, string(s)))
}
case runtime.Type_Info_Boolean:
switch b in a {
case bool: return _encode_bool(e.writer, b)
case b8: return _encode_bool(e.writer, bool(b))
case b16: return _encode_bool(e.writer, bool(b))
case b32: return _encode_bool(e.writer, bool(b))
case b64: return _encode_bool(e.writer, bool(b))
}
case runtime.Type_Info_Array:
if info.elem.id == byte {
raw := ([^]byte)(v.data)
return err_conv(_encode_bytes(e, raw[:info.count]))
}
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
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
}
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
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
}
return
case runtime.Type_Info_Dynamic_Array:
if info.elem.id == byte {
raw := (^[dynamic]byte)(v.data)
return err_conv(_encode_bytes(e, raw[:]))
}
array := (^mem.Raw_Dynamic_Array)(v.data)
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
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
}
return
case runtime.Type_Info_Slice:
if info.elem.id == byte {
raw := (^[]byte)(v.data)
return err_conv(_encode_bytes(e, raw^))
}
array := (^mem.Raw_Slice)(v.data)
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
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
}
return
case runtime.Type_Info_Map:
m := (^mem.Raw_Map)(v.data)
err_conv(_encode_u64(e, u64(runtime.map_len(m^)), .Map)) or_return
if m != nil {
if info.map_info == nil {
return _unsupported(v.id, nil)
}
map_cap := uintptr(runtime.map_cap(m^))
ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info)
if .Deterministic_Map_Sorting not_in e.flags {
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index))
marshal_into(e, any{ key, info.key.id }) or_return
marshal_into(e, any{ value, info.value.id }) or_return
}
return
}
// Deterministic_Map_Sorting needs us to sort the entries by the byte contents of the
// encoded key.
//
// This means we have to store and sort them before writing incurring extra (temporary) allocations.
//
// If the map key is a `string` or `cstring` we only allocate space for a dynamic array of entries
// we sort.
//
// If the map key is of another type we also allocate space for encoding the key into.
// To sort a string/cstring we need to first sort by their encoded header/length.
// This fits in 9 bytes at most.
pre_key :: #force_inline proc(e: Encoder, str: string) -> (res: [10]byte) {
e := e
builder := strings.builder_from_slice(res[:])
e.writer = strings.to_stream(&builder)
assert(_encode_u64(e, u64(len(str)), .Text) == nil)
res[9] = u8(len(builder.buf))
assert(res[9] < 10)
return
}
Encoded_Entry_Fast :: struct($T: typeid) {
pre_key: [10]byte,
key: T,
val_idx: uintptr,
}
Encoded_Entry :: struct {
key: ^[dynamic]byte,
val_idx: uintptr,
}
switch info.key.id {
case string:
entries := make([dynamic]Encoded_Entry_Fast(^[]byte), 0, map_cap, e.temp_allocator) or_return
defer delete(entries)
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := (^[]byte)(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
append(&entries, Encoded_Entry_Fast(^[]byte){
pre_key = pre_key(e, string(key^)),
key = key,
val_idx = bucket_index,
})
}
slice.sort_by_cmp(entries[:], proc(a, b: Encoded_Entry_Fast(^[]byte)) -> slice.Ordering {
a, b := a, b
pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]]))
if pre_cmp != .Equal {
return pre_cmp
}
return slice.Ordering(bytes.compare(a.key^, b.key^))
})
for &entry in entries {
io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return
io.write_full(e.writer, entry.key^) or_return
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx))
marshal_into(e, any{ value, info.value.id }) or_return
}
return
case cstring:
entries := make([dynamic]Encoded_Entry_Fast(^cstring), 0, map_cap, e.temp_allocator) or_return
defer delete(entries)
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := (^cstring)(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
append(&entries, Encoded_Entry_Fast(^cstring){
pre_key = pre_key(e, string(key^)),
key = key,
val_idx = bucket_index,
})
}
slice.sort_by_cmp(entries[:], proc(a, b: Encoded_Entry_Fast(^cstring)) -> slice.Ordering {
a, b := a, b
pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]]))
if pre_cmp != .Equal {
return pre_cmp
}
ab := transmute([]byte)string(a.key^)
bb := transmute([]byte)string(b.key^)
return slice.Ordering(bytes.compare(ab, bb))
})
for &entry in entries {
io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return
io.write_full(e.writer, transmute([]byte)string(entry.key^)) or_return
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx))
marshal_into(e, any{ value, info.value.id }) or_return
}
return
case:
entries := make([dynamic]Encoded_Entry, 0, map_cap, e.temp_allocator) or_return
defer delete(entries)
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
key_builder := strings.builder_make(0, 8, e.temp_allocator) or_return
marshal_into(Encoder{e.flags, strings.to_stream(&key_builder), e.temp_allocator}, any{ key, info.key.id }) or_return
append(&entries, Encoded_Entry{ &key_builder.buf, bucket_index }) or_return
}
slice.sort_by_cmp(entries[:], proc(a, b: Encoded_Entry) -> slice.Ordering {
return slice.Ordering(bytes.compare(a.key[:], b.key[:]))
})
for entry in entries {
io.write_full(e.writer, entry.key[:]) or_return
delete(entry.key^)
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx))
marshal_into(e, any{ value, info.value.id }) or_return
}
return
}
}
case runtime.Type_Info_Struct:
switch vv in v {
case Tag: return err_conv(_encode_tag(e, vv))
}
field_name :: #force_inline proc(info: runtime.Type_Info_Struct, i: int) -> string {
if cbor_name := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor")); cbor_name != "" {
return cbor_name
} else {
return info.names[i]
}
}
marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, name: string, i: int) -> Marshal_Error {
err_conv(_encode_text(e, name)) or_return
id := info.types[i].id
data := rawptr(uintptr(v.data) + info.offsets[i])
field_any := any{data, id}
if tag := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor_tag")); tag != "" {
if impl, ok := _tag_implementations_id[tag]; ok {
return impl->marshal(e, field_any)
}
nr, ok := strconv.parse_u64_of_base(tag, 10)
if !ok { return .Invalid_CBOR_Tag }
if impl, nok := _tag_implementations_nr[nr]; nok {
return impl->marshal(e, field_any)
}
err_conv(_encode_u64(e, nr, .Tag)) or_return
}
return marshal_into(e, field_any)
}
n: u64; {
for _, i in info.names {
if field_name(info, i) != "-" {
n += 1
}
}
err_conv(_encode_u64(e, n, .Map)) or_return
}
if .Deterministic_Map_Sorting in e.flags {
Name :: struct {
name: string,
field: int,
}
entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return
defer delete(entries)
for _, i in info.names {
fname := field_name(info, i)
if fname == "-" {
continue
}
append(&entries, Name{fname, i}) or_return
}
// Sort lexicographic on the bytes of the key.
slice.sort_by_cmp(entries[:], proc(a, b: Name) -> slice.Ordering {
return slice.Ordering(bytes.compare(transmute([]byte)a.name, transmute([]byte)b.name))
})
for entry in entries {
marshal_entry(e, info, v, entry.name, entry.field) or_return
}
} else {
for _, i in info.names {
fname := field_name(info, i)
if fname == "-" {
continue
}
marshal_entry(e, info, v, fname, i) or_return
}
}
return
case runtime.Type_Info_Union:
switch vv in v {
case Value: return err_conv(encode(e, vv))
}
id := reflect.union_variant_typeid(v)
if v.data == nil || id == nil {
return _encode_nil(e.writer)
}
if len(info.variants) == 1 {
return marshal_into(e, any{v.data, id})
}
// Encode a non-nil multi-variant union as the `TAG_OBJECT_TYPE`.
// Which is a tag of an array, where the first element is the textual id/type of the object
// that follows it.
err_conv(_encode_u16(e, TAG_OBJECT_TYPE, .Tag)) or_return
_encode_u8(e.writer, 2, .Array) or_return
vti := reflect.union_variant_type_info(v)
#partial switch vt in vti.variant {
case reflect.Type_Info_Named:
err_conv(_encode_text(e, vt.name)) or_return
case:
builder := strings.builder_make(e.temp_allocator) or_return
defer strings.builder_destroy(&builder)
reflect.write_type(&builder, vti)
err_conv(_encode_text(e, strings.to_string(builder))) or_return
}
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)
switch ti.size * 8 {
case 0:
return _encode_u8(e.writer, 0)
case 8:
x := (^u8)(v.data)^
return _encode_u8(e.writer, x)
case 16:
x := (^u16)(v.data)^
if do_byte_swap { x = intrinsics.byte_swap(x) }
return err_conv(_encode_u16(e, x))
case 32:
x := (^u32)(v.data)^
if do_byte_swap { x = intrinsics.byte_swap(x) }
return err_conv(_encode_u32(e, x))
case 64:
x := (^u64)(v.data)^
if do_byte_swap { x = intrinsics.byte_swap(x) }
return err_conv(_encode_u64(e, x))
case:
panic("unknown bit_size size")
}
}
return _unsupported(v.id, nil)
}
+381
View File
@@ -0,0 +1,381 @@
package encoding_cbor
import "base:runtime"
import "core:encoding/base64"
import "core:io"
import "core:math"
import "core:math/big"
import "core:mem"
import "core:reflect"
import "core:strings"
import "core:time"
// Tags defined in RFC 7049 that we provide implementations for.
// UTC time in seconds, unmarshalled into a `core:time` `time.Time` or integer.
// Use the struct tag `cbor_tag:"1"` or `cbor_tag:"epoch"` to have your `time.Time` field en/decoded as epoch time.
TAG_EPOCH_TIME_NR :: 1
TAG_EPOCH_TIME_ID :: "epoch"
// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal.
// These fields use this tag by default, no struct tag required.
TAG_UNSIGNED_BIG_NR :: 2
// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal.
// These fields use this tag by default, no struct tag required.
TAG_NEGATIVE_BIG_NR :: 3
// TAG_DECIMAL_FRACTION :: 4 // NOTE: We could probably implement this with `math/fixed`.
// Sometimes it is beneficial to carry an embedded CBOR data item that is not meant to be decoded
// immediately at the time the enclosing data item is being decoded. Tag number 24 (CBOR data item)
// can be used to tag the embedded byte string as a single data item encoded in CBOR format.
// Use the struct tag `cbor_tag:"24"` or `cbor_tag:"cbor"` to keep a non-decoded field (string or bytes) of raw CBOR.
TAG_CBOR_NR :: 24
TAG_CBOR_ID :: "cbor"
// The contents of this tag are base64 encoded during marshal and decoded during unmarshal.
// Use the struct tag `cbor_tag:"34"` or `cbor_tag:"base64"` to have your field string or bytes field en/decoded as base64.
TAG_BASE64_NR :: 34
TAG_BASE64_ID :: "base64"
// A tag that is used to detect the contents of a binary buffer (like a file) are CBOR.
// This tag would wrap everything else, decoders can then check for this header and see if the
// given content is definitely CBOR.
// Added by the encoder if it has the flag `.Self_Described_CBOR`, decoded by default.
TAG_SELF_DESCRIBED_CBOR :: 55799
// A tag that is used to assign a textual type to the object following it.
// The tag's value must be an array of 2 items, where the first is text (describing the following type)
// and the second is any valid CBOR value.
//
// See the registration: https://datatracker.ietf.org/doc/draft-rundgren-cotx/05/
//
// We use this in Odin to marshal and unmarshal unions.
TAG_OBJECT_TYPE :: 1010
// A tag implementation that handles marshals and unmarshals for the tag it is registered on.
Tag_Implementation :: struct {
data: rawptr,
unmarshal: Tag_Unmarshal_Proc,
marshal: Tag_Marshal_Proc,
}
// Procedure responsible for umarshalling the tag out of the reader into the given `any`.
Tag_Unmarshal_Proc :: #type proc(self: ^Tag_Implementation, d: Decoder, tag_nr: Tag_Number, v: any) -> Unmarshal_Error
// Procedure responsible for marshalling the tag in the given `any` into the given encoder.
Tag_Marshal_Proc :: #type proc(self: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error
// When encountering a tag in the CBOR being unmarshalled, the implementation is used to unmarshal it.
// When encountering a struct tag like `cbor_tag:"Tag_Number"`, the implementation is used to marshal it.
_tag_implementations_nr: map[Tag_Number]Tag_Implementation
// Same as the number implementations but friendlier to use as a struct tag.
// Instead of `cbor_tag:"34"` you can use `cbor_tag:"base64"`.
_tag_implementations_id: map[string]Tag_Implementation
// Tag implementations that are always used by a type, if that type is encountered in marshal it
// will rely on the implementation to marshal it.
//
// This is good for types that don't make sense or can't marshal in its default form.
_tag_implementations_type: map[typeid]Tag_Implementation
// Register a custom tag implementation to be used when marshalling that type and unmarshalling that tag number.
tag_register_type :: proc(impl: Tag_Implementation, nr: Tag_Number, type: typeid) {
_tag_implementations_nr[nr] = impl
_tag_implementations_type[type] = impl
}
// Register a custom tag implementation to be used when marshalling that tag number or marshalling
// a field with the struct tag `cbor_tag:"nr"`.
tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string) {
_tag_implementations_nr[nr] = impl
_tag_implementations_id[id] = impl
}
// Controls initialization of default tag implementations.
// JS and WASI default to a panic allocator so we don't want to do it on those.
INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)
tags_initialize_defaults :: proc() {
tags_register_defaults()
}
// Registers tags that have implementations provided by this package.
// This is done by default and can be controlled with the `CBOR_INITIALIZE_DEFAULT_TAGS` define.
tags_register_defaults :: proc() {
tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID)
tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID)
tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID)
// These following tags are registered at the type level and don't require an opt-in struct tag.
// Encoding these types on its own make no sense or no data is lost to encode it.
// En/Decoding of `big.Int` fields by default.
tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_UNSIGNED_BIG_NR, big.Int)
tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_NEGATIVE_BIG_NR, big.Int)
}
// Tag number 1 contains a numerical value counting the number of seconds from 1970-01-01T00:00Z
// in UTC time to the represented point in civil time.
//
// See RFC 8949 section 3.4.2.
@(private)
tag_time_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) {
hdr := _decode_header(d.reader) or_return
#partial switch hdr {
case .U8, .U16, .U32, .U64, .Neg_U8, .Neg_U16, .Neg_U32, .Neg_U64:
switch &dst in v {
case time.Time:
i: i64
_unmarshal_any_ptr(d, &i, hdr) or_return
dst = time.unix(i64(i), 0)
return
case:
return _unmarshal_value(d, v, hdr)
}
case .F16, .F32, .F64:
switch &dst in v {
case time.Time:
f: f64
_unmarshal_any_ptr(d, &f, hdr) or_return
whole, fract := math.modf(f)
dst = time.unix(i64(whole), i64(fract * 1e9))
return
case:
return _unmarshal_value(d, v, hdr)
}
case:
maj, add := _header_split(hdr)
if maj == .Other {
i := _decode_tiny_u8(add) or_return
switch &dst in v {
case time.Time:
dst = time.unix(i64(i), 0)
case:
if _assign_int(v, i) { return }
}
}
// Only numbers and floats are allowed in this tag.
return .Bad_Tag_Value
}
return _unsupported(v, hdr)
}
@(private)
tag_time_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
switch vv in v {
case time.Time:
// NOTE: we lose precision here, which is one of the reasons for this tag being opt-in.
i := time.time_to_unix(vv)
_encode_u8(e.writer, TAG_EPOCH_TIME_NR, .Tag) or_return
return err_conv(_encode_uint(e, _int_to_uint(i)))
case:
unreachable()
}
}
@(private)
tag_big_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, tnr: Tag_Number, v: any) -> (err: Unmarshal_Error) {
hdr := _decode_header(d.reader) or_return
maj, add := _header_split(hdr)
if maj != .Bytes {
// Only bytes are supported in this tag.
return .Bad_Tag_Value
}
switch &dst in v {
case big.Int:
bytes := err_conv(_decode_bytes(d, add)) or_return
defer delete(bytes)
if err := big.int_from_bytes_big(&dst, bytes); err != nil {
return .Bad_Tag_Value
}
if tnr == TAG_NEGATIVE_BIG_NR {
dst.sign = .Negative
}
return
}
return _unsupported(v, hdr)
}
@(private)
tag_big_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
switch &vv in v {
case big.Int:
if !big.int_is_initialized(&vv) {
_encode_u8(e.writer, TAG_UNSIGNED_BIG_NR, .Tag) or_return
return _encode_u8(e.writer, 0, .Bytes)
}
// NOTE: using the panic_allocator because all procedures should only allocate if the Int
// is uninitialized (which we checked).
is_neg, err := big.is_negative(&vv, mem.panic_allocator())
assert(err == nil, "should only error if not initialized, which has been checked")
tnr: u8 = TAG_NEGATIVE_BIG_NR if is_neg else TAG_UNSIGNED_BIG_NR
_encode_u8(e.writer, tnr, .Tag) or_return
size_in_bytes, berr := big.int_to_bytes_size(&vv, false, mem.panic_allocator())
assert(berr == nil, "should only error if not initialized, which has been checked")
assert(size_in_bytes >= 0)
err_conv(_encode_u64(e, u64(size_in_bytes), .Bytes)) or_return
for offset := (size_in_bytes*8)-8; offset >= 0; offset -= 8 {
bits, derr := big.int_bitfield_extract(&vv, offset, 8, mem.panic_allocator())
assert(derr == nil, "should only error if not initialized or invalid argument (offset and count), which won't happen")
io.write_full(e.writer, {u8(bits & 255)}) or_return
}
return nil
case: unreachable()
}
}
@(private)
tag_cbor_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> Unmarshal_Error {
hdr := _decode_header(d.reader) or_return
major, add := _header_split(hdr)
#partial switch major {
case .Bytes:
ti := reflect.type_info_base(type_info_of(v.id))
return _unmarshal_bytes(d, v, ti, hdr, add)
case: return .Bad_Tag_Value
}
}
@(private)
tag_cbor_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
_encode_u8(e.writer, TAG_CBOR_NR, .Tag) or_return
ti := runtime.type_info_base(type_info_of(v.id))
#partial switch t in ti.variant {
case runtime.Type_Info_String:
return marshal_into(e, v)
case runtime.Type_Info_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return .Bad_Tag_Value }
return marshal_into(e, v)
case runtime.Type_Info_Slice:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return .Bad_Tag_Value }
return marshal_into(e, v)
case runtime.Type_Info_Dynamic_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return .Bad_Tag_Value }
return marshal_into(e, v)
case:
return .Bad_Tag_Value
}
}
@(private)
tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) {
hdr := _decode_header(d.reader) or_return
major, add := _header_split(hdr)
ti := reflect.type_info_base(type_info_of(v.id))
if major != .Text && major != .Bytes {
return .Bad_Tag_Value
}
bytes := string(err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return)
defer delete(bytes, context.temp_allocator)
#partial switch t in ti.variant {
case reflect.Type_Info_String:
if t.is_cstring {
length := base64.decoded_len(bytes)
builder := strings.builder_make(0, length+1)
base64.decode_into(strings.to_stream(&builder), bytes) or_return
raw := (^cstring)(v.data)
raw^ = cstring(raw_data(builder.buf))
} else {
raw := (^string)(v.data)
raw^ = string(base64.decode(bytes) or_return)
}
return
case reflect.Type_Info_Slice:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
raw := (^[]byte)(v.data)
raw^ = base64.decode(bytes) or_return
return
case reflect.Type_Info_Dynamic_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
decoded := base64.decode(bytes) or_return
raw := (^mem.Raw_Dynamic_Array)(v.data)
raw.data = raw_data(decoded)
raw.len = len(decoded)
raw.cap = len(decoded)
raw.allocator = context.allocator
return
case reflect.Type_Info_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
if base64.decoded_len(bytes) > t.count { return _unsupported(v, hdr) }
slice := ([^]byte)(v.data)[:len(bytes)]
copy(slice, base64.decode(bytes) or_return)
return
}
return _unsupported(v, hdr)
}
@(private)
tag_base64_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
_encode_u8(e.writer, TAG_BASE64_NR, .Tag) or_return
ti := runtime.type_info_base(type_info_of(v.id))
a := any{v.data, ti.id}
bytes: []byte
switch val in a {
case string: bytes = transmute([]byte)val
case cstring: bytes = transmute([]byte)string(val)
case []byte: bytes = val
case [dynamic]byte: bytes = val[:]
case:
#partial switch t in ti.variant {
case runtime.Type_Info_Array:
if t.elem.id != byte { return .Bad_Tag_Value }
bytes = ([^]byte)(v.data)[:t.count]
case:
return .Bad_Tag_Value
}
}
out_len := base64.encoded_len(bytes)
err_conv(_encode_u64(e, u64(out_len), .Text)) or_return
return base64.encode_into(e.writer, bytes)
}
+932
View File
@@ -0,0 +1,932 @@
package encoding_cbor
import "base:intrinsics"
import "base:runtime"
import "core:io"
import "core:mem"
import "core:reflect"
import "core:strings"
import "core:unicode/utf8"
/*
Unmarshals the given CBOR into the given pointer using reflection.
Types that require allocation are allocated using the given allocator.
Some temporary allocations are done on the given `temp_allocator`, but, if you want to,
this can be set to a "normal" allocator, because the necessary `delete` and `free` calls are still made.
This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end.
Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag.
Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag.
Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature
of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only
do this when you own both sides of the encoding and are sure there can't be malicious bytes used as
an input.
*/
unmarshal :: proc {
unmarshal_from_reader,
unmarshal_from_string,
}
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
// Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
sr: strings.Reader
r := strings.to_reader(&sr, s)
err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
d := d
err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error {
context.allocator = allocator
context.temp_allocator = temp_allocator
v := v
if v == nil || v.id == nil {
return .Invalid_Parameter
}
v = reflect.any_base(v)
ti := type_info_of(v.id)
if !reflect.is_pointer(ti) || ti.id == rawptr {
return .Non_Pointer_Parameter
}
data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}
return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return))
}
_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) {
v := v
ti := reflect.type_info_base(type_info_of(v.id))
r := d.reader
// If it's a union with only one variant, then treat it as that variant
if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 {
#partial switch hdr {
case .Nil, .Undefined, nil: // no-op.
case:
variant := u.variants[0]
v.id = variant.id
ti = reflect.type_info_base(variant)
if !reflect.is_pointer_internally(variant) {
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
assert(_assign_int(tag, 1))
}
}
}
// Allow generic unmarshal by doing it into a `Value`.
switch &dst in v {
case Value:
dst = err_conv(_decode_from_decoder(d, hdr)) or_return
return
}
switch hdr {
case .U8:
decoded := _decode_u8(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .U16:
decoded := _decode_u16(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .U32:
decoded := _decode_u32(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .U64:
decoded := _decode_u64(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .Neg_U8:
decoded := Negative_U8(_decode_u8(r) or_return)
switch &dst in v {
case Negative_U8:
dst = decoded
return
case Negative_U16:
dst = Negative_U16(decoded)
return
case Negative_U32:
dst = Negative_U32(decoded)
return
case Negative_U64:
dst = Negative_U64(decoded)
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Neg_U16:
decoded := Negative_U16(_decode_u16(r) or_return)
switch &dst in v {
case Negative_U16:
dst = decoded
return
case Negative_U32:
dst = Negative_U32(decoded)
return
case Negative_U64:
dst = Negative_U64(decoded)
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Neg_U32:
decoded := Negative_U32(_decode_u32(r) or_return)
switch &dst in v {
case Negative_U32:
dst = decoded
return
case Negative_U64:
dst = Negative_U64(decoded)
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Neg_U64:
decoded := Negative_U64(_decode_u64(r) or_return)
switch &dst in v {
case Negative_U64:
dst = decoded
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Simple:
decoded := _decode_simple(r) or_return
// NOTE: Because this is a special type and not to be treated as a general integer,
// We only put the value of it in fields that are explicitly of type `Simple`.
switch &dst in v {
case Simple:
dst = decoded
return
case:
return _unsupported(v, hdr)
}
case .F16:
decoded := _decode_f16(r) or_return
if !_assign_float(v, decoded) { return _unsupported(v, hdr) }
return
case .F32:
decoded := _decode_f32(r) or_return
if !_assign_float(v, decoded) { return _unsupported(v, hdr) }
return
case .F64:
decoded := _decode_f64(r) or_return
if !_assign_float(v, decoded) { return _unsupported(v, hdr) }
return
case .True:
if !_assign_bool(v, true) { return _unsupported(v, hdr) }
return
case .False:
if !_assign_bool(v, false) { return _unsupported(v, hdr) }
return
case .Nil, .Undefined:
mem.zero(v.data, ti.size)
return
case .Break:
return .Break
}
maj, add := _header_split(hdr)
switch maj {
case .Unsigned:
decoded := _decode_tiny_u8(add) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr, add) }
return
case .Negative:
decoded := Negative_U8(_decode_tiny_u8(add) or_return)
switch &dst in v {
case Negative_U8:
dst = decoded
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr, add) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr, add) }
return
case .Other:
decoded := _decode_tiny_simple(add) or_return
// NOTE: Because this is a special type and not to be treated as a general integer,
// We only put the value of it in fields that are explicitly of type `Simple`.
switch &dst in v {
case Simple:
dst = decoded
return
case:
return _unsupported(v, hdr, add)
}
case .Tag:
switch &dst in v {
case ^Tag:
tval := err_conv(_decode_tag_ptr(d, add)) or_return
if t, is_tag := tval.(^Tag); is_tag {
dst = t
return
}
destroy(tval)
return .Bad_Tag_Value
case Tag:
t := err_conv(_decode_tag(d, add)) or_return
if t, is_tag := t.?; is_tag {
dst = t
return
}
return .Bad_Tag_Value
}
nr := err_conv(_decode_uint_as_u64(r, add)) or_return
// Custom tag implementations.
if impl, ok := _tag_implementations_nr[nr]; ok {
return impl->unmarshal(d, nr, v)
} else if nr == TAG_OBJECT_TYPE {
return _unmarshal_union(d, v, ti, hdr)
} else {
// Discard the tag info and unmarshal as its value.
return _unmarshal_value(d, v, _decode_header(r) or_return)
}
return _unsupported(v, hdr, add)
case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add)
case .Text: return _unmarshal_string(d, v, ti, hdr, add)
case .Array: return _unmarshal_array(d, v, ti, hdr, add)
case .Map: return _unmarshal_map(d, v, ti, hdr, add)
case: return .Bad_Major
}
}
_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
#partial switch t in ti.variant {
case reflect.Type_Info_String:
bytes := err_conv(_decode_bytes(d, add)) or_return
if t.is_cstring {
raw := (^cstring)(v.data)
assert_safe_for_cstring(string(bytes))
raw^ = cstring(raw_data(bytes))
} else {
// String has same memory layout as a slice, so we can directly use it as a slice.
raw := (^mem.Raw_String)(v.data)
raw^ = transmute(mem.Raw_String)bytes
}
return
case reflect.Type_Info_Slice:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
bytes := err_conv(_decode_bytes(d, add)) or_return
raw := (^mem.Raw_Slice)(v.data)
raw^ = transmute(mem.Raw_Slice)bytes
return
case reflect.Type_Info_Dynamic_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
bytes := err_conv(_decode_bytes(d, add)) or_return
raw := (^mem.Raw_Dynamic_Array)(v.data)
raw.data = raw_data(bytes)
raw.len = len(bytes)
raw.cap = len(bytes)
raw.allocator = context.allocator
return
case reflect.Type_Info_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
bytes := err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return
defer delete(bytes, context.temp_allocator)
if len(bytes) > t.count { return _unsupported(v, hdr) }
// Copy into array type, delete original.
slice := ([^]byte)(v.data)[:len(bytes)]
n := copy(slice, bytes)
assert(n == len(bytes))
return
}
return _unsupported(v, hdr)
}
_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
#partial switch t in ti.variant {
case reflect.Type_Info_String:
text := err_conv(_decode_text(d, add)) or_return
if t.is_cstring {
raw := (^cstring)(v.data)
assert_safe_for_cstring(text)
raw^ = cstring(raw_data(text))
} else {
raw := (^string)(v.data)
raw^ = text
}
return
// Enum by its variant name.
case reflect.Type_Info_Enum:
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
defer delete(text, context.temp_allocator)
for name, i in t.names {
if name == text {
if !_assign_int(any{v.data, ti.id}, t.values[i]) { return _unsupported(v, hdr) }
return
}
}
case reflect.Type_Info_Rune:
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
defer delete(text, context.temp_allocator)
r := (^rune)(v.data)
dr, n := utf8.decode_rune(text)
if dr == utf8.RUNE_ERROR || n < len(text) {
return _unsupported(v, hdr)
}
r^ = dr
return
}
return _unsupported(v, hdr)
}
_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
assign_array :: proc(
d: Decoder,
da: ^mem.Raw_Dynamic_Array,
elemt: ^reflect.Type_Info,
length: int,
growable := true,
) -> (out_of_space: bool, err: Unmarshal_Error) {
for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
elem := any{elem_ptr, elemt.id}
hdr := _decode_header(d.reader) or_return
// Double size if out of capacity.
if da.cap <= da.len {
// Not growable, error out.
if !growable { return true, .Out_Of_Memory }
cap := 2 * da.cap
ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap)
// NOTE: Might be lying here, but it is at least an allocator error.
if !ok { return false, .Out_Of_Memory }
}
err = _unmarshal_value(d, elem, hdr)
if length == -1 && err == .Break { break }
if err != nil { return }
da.len += 1
}
return false, nil
}
// Allow generically storing the values array.
switch &dst in v {
case ^Array:
dst = err_conv(_decode_array_ptr(d, add)) or_return
return
case Array:
dst = err_conv(_decode_array(d, add)) or_return
return
}
#partial switch t in ti.variant {
case reflect.Type_Info_Slice:
length, scap := err_conv(_decode_len_container(d, add)) or_return
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
defer if err != nil { mem.free_bytes(data) }
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
assign_array(d, &da, t.elem, length) or_return
if .Shrink_Excess in d.flags {
// Ignoring an error here, but this is not critical to succeed.
_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len)
}
raw := (^mem.Raw_Slice)(v.data)
raw.data = da.data
raw.len = da.len
return
case reflect.Type_Info_Dynamic_Array:
length, scap := err_conv(_decode_len_container(d, add)) or_return
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
defer if err != nil { mem.free_bytes(data) }
raw := (^mem.Raw_Dynamic_Array)(v.data)
raw.data = raw_data(data)
raw.len = 0
raw.cap = length
raw.allocator = context.allocator
_ = assign_array(d, raw, t.elem, length) or_return
if .Shrink_Excess in d.flags {
// Ignoring an error here, but this is not critical to succeed.
_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len)
}
return
case reflect.Type_Info_Array:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, t.count)
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Enumerated_Array:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, t.count)
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Complex:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, 2)
if length > 2 {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator }
info: ^runtime.Type_Info
switch ti.id {
case complex32: info = type_info_of(f16)
case complex64: info = type_info_of(f32)
case complex128: info = type_info_of(f64)
case: unreachable()
}
out_of_space := assign_array(d, &da, info, 2, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Quaternion:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, 4)
if length > 4 {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator }
info: ^runtime.Type_Info
switch ti.id {
case quaternion64: info = type_info_of(f16)
case quaternion128: info = type_info_of(f32)
case quaternion256: info = type_info_of(f64)
case: unreachable()
}
out_of_space := assign_array(d, &da, info, 4, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case: return _unsupported(v, hdr)
}
}
_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
r := d.reader
decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) {
entry_hdr := _decode_header(d.reader) or_return
entry_maj, entry_add := _header_split(entry_hdr)
#partial switch entry_maj {
case .Text:
k = err_conv(_decode_text(d, entry_add, allocator)) or_return
return
case .Bytes:
bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return
k = string(bytes)
return
case:
err = _unsupported(v, entry_hdr)
return
}
}
// Allow generically storing the map array.
switch &dst in v {
case ^Map:
dst = err_conv(_decode_map_ptr(d, add)) or_return
return
case Map:
dst = err_conv(_decode_map(d, add)) or_return
return
}
#partial switch t in ti.variant {
case reflect.Type_Info_Struct:
if t.is_raw_union {
return _unsupported(v, hdr)
}
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
if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break {
break
} else if kerr != nil {
err = kerr
return
} else {
key = keyv
}
defer delete(key, context.temp_allocator)
// Find matching field.
use_field_idx := -1
{
for field, field_idx in fields {
tag_value := string(reflect.struct_tag_get(field.tag, "cbor"))
if tag_value == "-" {
continue
}
if key == tag_value {
use_field_idx = field_idx
break
}
if key == field.name {
// No break because we want to still check remaining struct tags.
use_field_idx = field_idx
}
}
// Skips unused map entries.
if use_field_idx < 0 {
continue
}
}
field := fields[use_field_idx]
// name := field.name
ptr := rawptr(uintptr(v.data) + field.offset)
fany := any{ptr, field.type.id}
_unmarshal_value(d, fany, _decode_header(r) or_return) or_return
}
return
case reflect.Type_Info_Map:
if !reflect.is_string(t.key) {
return _unsupported(v, hdr)
}
raw_map := (^mem.Raw_Map)(v.data)
if raw_map.allocator.procedure == nil {
raw_map.allocator = context.allocator
}
defer if err != nil {
_ = runtime.map_free_dynamic(raw_map^, t.map_info)
}
length, scap := err_conv(_decode_len_container(d, add)) or_return
unknown := length == -1
if !unknown {
// Reserve space before setting so we can return allocation errors and be efficient on big maps.
new_len := uintptr(min(scap, runtime.map_len(raw_map^)+length))
runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return
}
// Temporary memory to unmarshal keys into before inserting them into the map.
elem_backing := mem.alloc_bytes_non_zeroed(t.value.size, t.value.align, context.temp_allocator) or_return
defer delete(elem_backing, context.temp_allocator)
map_backing_value := any{raw_data(elem_backing), t.value.id}
for idx := 0; unknown || idx < length; idx += 1 {
// Decode key, keys can only be strings.
key: string
if keyv, kerr := decode_key(d, v); unknown && kerr == .Break {
break
} else if kerr != nil {
err = kerr
return
} else {
key = keyv
}
if unknown || idx > scap {
// Reserve space for new element so we can return allocator errors.
new_len := uintptr(runtime.map_len(raw_map^)+1)
runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return
}
mem.zero_slice(elem_backing)
_unmarshal_value(d, map_backing_value, _decode_header(r) or_return) or_return
key_ptr := rawptr(&key)
key_cstr: cstring
if reflect.is_cstring(t.key) {
assert_safe_for_cstring(key)
key_cstr = cstring(raw_data(key))
key_ptr = &key_cstr
}
set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
// We already reserved space for it, so this shouldn't fail.
assert(set_ptr != nil)
}
if .Shrink_Excess in d.flags {
_, _ = runtime.map_shrink_dynamic(raw_map, t.map_info)
}
return
case:
return _unsupported(v, hdr)
}
}
// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
// contains an array of exactly two elements, the first is a textual representation of the following
// CBOR value's type.
_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
r := d.reader
#partial switch t in ti.variant {
case reflect.Type_Info_Union:
idhdr: Header
target_name: string
{
vhdr := _decode_header(r) or_return
vmaj, vadd := _header_split(vhdr)
if vmaj != .Array {
return .Bad_Tag_Value
}
n_items, _ := err_conv(_decode_len_container(d, vadd)) or_return
if n_items != 2 {
return .Bad_Tag_Value
}
idhdr = _decode_header(r) or_return
idmaj, idadd := _header_split(idhdr)
if idmaj != .Text {
return .Bad_Tag_Value
}
target_name = err_conv(_decode_text(d, idadd, context.temp_allocator)) or_return
}
defer delete(target_name, context.temp_allocator)
for variant, i in t.variants {
tag := i64(i)
if !t.no_nil {
tag += 1
}
#partial switch vti in variant.variant {
case reflect.Type_Info_Named:
if vti.name == target_name {
reflect.set_union_variant_raw_tag(v, tag)
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
}
case:
builder := strings.builder_make(context.temp_allocator)
defer strings.builder_destroy(&builder)
reflect.write_type(&builder, variant)
variant_name := strings.to_string(builder)
if variant_name == target_name {
reflect.set_union_variant_raw_tag(v, tag)
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
}
}
}
// No variant matched.
return _unsupported(v, idhdr)
case:
// Not a union.
return _unsupported(v, hdr)
}
}
_assign_int :: proc(val: any, i: $T) -> bool {
v := reflect.any_core(val)
// NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a
// less strict encoding?.
switch &dst in v {
case i8: dst = i8 (i)
case i16: dst = i16 (i)
case i16le: dst = i16le (i)
case i16be: dst = i16be (i)
case i32: dst = i32 (i)
case i32le: dst = i32le (i)
case i32be: dst = i32be (i)
case i64: dst = i64 (i)
case i64le: dst = i64le (i)
case i64be: dst = i64be (i)
case i128: dst = i128 (i)
case i128le: dst = i128le (i)
case i128be: dst = i128be (i)
case u8: dst = u8 (i)
case u16: dst = u16 (i)
case u16le: dst = u16le (i)
case u16be: dst = u16be (i)
case u32: dst = u32 (i)
case u32le: dst = u32le (i)
case u32be: dst = u32be (i)
case u64: dst = u64 (i)
case u64le: dst = u64le (i)
case u64be: dst = u64be (i)
case u128: dst = u128 (i)
case u128le: dst = u128le (i)
case u128be: dst = u128be (i)
case int: dst = int (i)
case uint: dst = uint (i)
case uintptr: dst = uintptr(i)
case:
ti := type_info_of(v.id)
if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok {
do_byte_swap := !reflect.bit_set_is_big_endian(v)
switch ti.size * 8 {
case 0: // no-op.
case 8:
x := (^u8)(v.data)
x^ = u8(i)
case 16:
x := (^u16)(v.data)
x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i)
case 32:
x := (^u32)(v.data)
x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i)
case 64:
x := (^u64)(v.data)
x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i)
case:
panic("unknown bit_size size")
}
return true
}
return false
}
return true
}
_assign_float :: proc(val: any, f: $T) -> bool {
v := reflect.any_core(val)
// NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a
// less strict encoding?.
switch &dst in v {
case f16: dst = f16 (f)
case f16le: dst = f16le(f)
case f16be: dst = f16be(f)
case f32: dst = f32 (f)
case f32le: dst = f32le(f)
case f32be: dst = f32be(f)
case f64: dst = f64 (f)
case f64le: dst = f64le(f)
case f64be: dst = f64be(f)
case complex32: dst = complex(f16(f), 0)
case complex64: dst = complex(f32(f), 0)
case complex128: dst = complex(f64(f), 0)
case quaternion64: dst = quaternion(w=f16(f), x=0, y=0, z=0)
case quaternion128: dst = quaternion(w=f32(f), x=0, y=0, z=0)
case quaternion256: dst = quaternion(w=f64(f), x=0, y=0, z=0)
case: return false
}
return true
}
_assign_bool :: proc(val: any, b: bool) -> bool {
v := reflect.any_core(val)
switch &dst in v {
case bool: dst = bool(b)
case b8: dst = b8 (b)
case b16: dst = b16 (b)
case b32: dst = b32 (b)
case b64: dst = b64 (b)
case: return false
}
return true
}
// Sanity check that the decoder added a nil byte to the end.
@(private, disabled=ODIN_DISABLE_ASSERT)
assert_safe_for_cstring :: proc(s: string, loc := #caller_location) {
assert(([^]byte)(raw_data(s))[len(s)] == 0, loc = loc)
}
+20 -6
View File
@@ -1,6 +1,6 @@
// package csv reads and writes comma-separated values (CSV) files.
// This package supports the format described in RFC 4180 <https://tools.ietf.org/html/rfc4180.html>
package csv
package encoding_csv
import "core:bufio"
import "core:bytes"
@@ -91,7 +91,10 @@ DEFAULT_RECORD_BUFFER_CAPACITY :: 256
// reader_init initializes a new Reader from r
reader_init :: proc(reader: ^Reader, r: io.Reader, buffer_allocator := context.allocator) {
reader.comma = ','
switch reader.comma {
case '\x00', '\n', '\r', 0xfffd:
reader.comma = ','
}
context.allocator = buffer_allocator
reserve(&reader.record_buffer, DEFAULT_RECORD_BUFFER_CAPACITY)
@@ -121,6 +124,7 @@ reader_destroy :: proc(r: ^Reader) {
// read reads a single record (a slice of fields) from r
//
// All \r\n sequences are normalized to \n, including multi-line field
@(require_results)
read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, err: Error) {
if r.reuse_record {
record, err = _read_record(r, &r.last_record, allocator)
@@ -133,6 +137,7 @@ read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, e
}
// is_io_error checks where an Error is a specific io.Error kind
@(require_results)
is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
if v, ok := err.(io.Error); ok {
return v == io_err
@@ -140,10 +145,10 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
return false
}
// read_all reads all the remaining records from r.
// Each record is a slice of fields.
// read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error
@(require_results)
read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) {
context.allocator = allocator
records: [dynamic][]string
@@ -153,13 +158,18 @@ read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Err
return records[:], nil
}
if rerr != nil {
return nil, rerr
// allow for a partial read
if record != nil {
append(&records, record)
}
return records[:], rerr
}
append(&records, record)
}
}
// read reads a single record (a slice of fields) from the provided input.
@(require_results)
read_from_string :: proc(input: string, record_allocator := context.allocator, buffer_allocator := context.allocator) -> (record: []string, n: int, err: Error) {
ir: strings.Reader
strings.reader_init(&ir, input)
@@ -175,6 +185,7 @@ read_from_string :: proc(input: string, record_allocator := context.allocator, b
// read_all reads all the remaining records from the provided input.
@(require_results)
read_all_from_string :: proc(input: string, records_allocator := context.allocator, buffer_allocator := context.allocator) -> ([][]string, Error) {
ir: strings.Reader
strings.reader_init(&ir, input)
@@ -186,7 +197,7 @@ read_all_from_string :: proc(input: string, records_allocator := context.allocat
return read_all(&r, records_allocator)
}
@private
@(private, require_results)
is_valid_delim :: proc(r: rune) -> bool {
switch r {
case 0, '"', '\r', '\n', utf8.RUNE_ERROR:
@@ -195,8 +206,9 @@ is_valid_delim :: proc(r: rune) -> bool {
return utf8.valid_rune(r)
}
@private
@(private, require_results)
_read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) {
@(require_results)
read_line :: proc(r: ^Reader) -> ([]byte, io.Error) {
if !r.multiline_fields {
line, err := bufio.reader_read_slice(&r.r, '\n')
@@ -266,6 +278,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all
unreachable()
}
@(require_results)
length_newline :: proc(b: []byte) -> int {
if len(b) > 0 && b[len(b)-1] == '\n' {
return 1
@@ -273,6 +286,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all
return 0
}
@(require_results)
next_rune :: proc(b: []byte) -> rune {
r, _ := utf8.decode_rune(b)
return r
+5 -2
View File
@@ -1,4 +1,4 @@
package csv
package encoding_csv
import "core:io"
import "core:strings"
@@ -17,7 +17,10 @@ Writer :: struct {
// writer_init initializes a Writer that writes to w
writer_init :: proc(writer: ^Writer, w: io.Writer) {
writer.comma = ','
switch writer.comma {
case '\x00', '\n', '\r', 0xfffd:
writer.comma = ','
}
writer.w = w
}
+1 -1
View File
@@ -1,4 +1,4 @@
package unicode_entity
package encoding_unicode_entity
/*
A unicode entity encoder/decoder
+1 -1
View File
@@ -1,4 +1,4 @@
package unicode_entity
package encoding_unicode_entity
/*
------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------
+1 -1
View File
@@ -1,4 +1,4 @@
package hex
package encoding_hex
import "core:strings"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
import "core:math/bits"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
import "core:unicode/utf8"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:unicode/utf8"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:strings"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
import "core:math"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
+1 -1
View File
@@ -25,4 +25,4 @@
```
*/
package varint
package encoding_varint
+1 -1
View File
@@ -8,7 +8,7 @@
// package varint implements variable length integer encoding and decoding using
// the LEB128 format as used by DWARF debug info, Android .dex and other file formats.
package varint
package encoding_varint
// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file.
// Instead we'll set limits on the values we'll encode/decode
+1 -1
View File
@@ -1,4 +1,4 @@
package xml
package encoding_xml
/*
An XML 1.0 / 1.1 parser
+1 -1
View File
@@ -1,4 +1,4 @@
package xml
package encoding_xml
/*
An XML 1.0 / 1.1 parser
+1 -1
View File
@@ -1,4 +1,4 @@
package xml
package encoding_xml
/*
An XML 1.0 / 1.1 parser
+1 -1
View File
@@ -24,7 +24,7 @@ MAYBE:
List of contributors:
- Jeroen van Rijn: Initial implementation.
*/
package xml
package encoding_xml
// An XML 1.0 / 1.1 parser
import "core:bytes"
+27 -6
View File
@@ -1900,7 +1900,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
// fi.hash = false;
fi.indent += 1
if hash {
if !is_soa && hash {
io.write_byte(fi.writer, '\n', &fi.n)
}
defer {
@@ -1934,6 +1934,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
}
if hash && n > 0 {
io.write_byte(fi.writer, '\n', &fi.n)
}
for index in 0..<n {
if !hash && index > 0 { io.write_string(fi.writer, ", ", &fi.n) }
@@ -1942,9 +1945,23 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
if !hash && field_count > 0 { io.write_string(fi.writer, ", ", &fi.n) }
if hash {
fi.indent -= 1
fmt_write_indent(fi)
fi.indent += 1
}
io.write_string(fi.writer, base_type_name, &fi.n)
io.write_byte(fi.writer, '{', &fi.n)
defer io.write_byte(fi.writer, '}', &fi.n)
if hash { io.write_byte(fi.writer, '\n', &fi.n) }
defer {
if hash {
fi.indent -= 1
fmt_write_indent(fi)
fi.indent += 1
}
io.write_byte(fi.writer, '}', &fi.n)
if hash { io.write_string(fi.writer, ",\n", &fi.n) }
}
fi.record_level += 1
defer fi.record_level -= 1
@@ -2156,14 +2173,18 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named)
when ODIN_ERROR_POS_STYLE == .Default {
io.write_byte(fi.writer, '(', &fi.n)
io.write_int(fi.writer, int(a.line), 10, &fi.n)
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
if a.column != 0 {
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
}
io.write_byte(fi.writer, ')', &fi.n)
} else when ODIN_ERROR_POS_STYLE == .Unix {
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.line), 10, &fi.n)
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
if a.column != 0 {
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
}
io.write_byte(fi.writer, ':', &fi.n)
} else {
#panic("Unhandled ODIN_ERROR_POS_STYLE")
+4
View File
@@ -37,6 +37,8 @@ print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w
println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) }
// printf formats according to the specififed format string and writes to stdout
printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) }
// printfln formats according to the specified format string and writes to stdout, followed by a newline.
printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) }
// eprint formats using the default print settings and writes to stderr
eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) }
@@ -44,3 +46,5 @@ eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(
eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) }
// eprintf formats according to the specififed format string and writes to stderr
eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) }
// eprintfln formats according to the specified format string and writes to stderr, followed by a newline.
eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) }
+21 -18
View File
@@ -1,5 +1,6 @@
/*
Formats:
PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel)
PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value)
PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value)
@@ -7,27 +8,29 @@ Formats:
PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel)
Reading:
All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`
Specific formats use more fields
PGM, PPM, and PAM set `maxval` (maximum of 65535)
PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3)
PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats)
Currently doesn't support reading multiple images from one binary-format file
- All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`.
- Specific formats use more fields:
PGM, PPM, and PAM set `maxval` (maximum of 65535)
PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3)
PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats)
- Currently doesn't support reading multiple images from one binary-format file.
Writing:
You can use your own `Netpbm_Info` struct to control how images are written
All formats require the header field `format` to be specified
Additional header fields are required for specific formats
PGM, PPM, and PAM require `maxval` (maximum of 65535)
PAM also uses `tupltype`, though it may be left as default (empty or nil string)
PFM requires `scale`, and optionally `little_endian`
- You can use your own `Netpbm_Info` struct to control how images are written.
- All formats require the header field `format` to be specified.
- Additional header fields are required for specific formats:
PGM, PPM, and PAM require `maxval` (maximum of 65535)
PAM also uses `tupltype`, though it may be left as default (empty or nil string)
PFM requires `scale`, and optionally `little_endian`
Some syntax differences from the specifications:
`channels` stores the number of values per pixel, what the PAM specification calls `depth`
`depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise)
`scale` and `little_endian` are separated, so the `header` will always store a positive `scale`
`little_endian` will only be true for a negative `scale` PFM, every other format will be false
`little_endian` only describes the netpbm data being read/written, the image buffer will be native
*/
- `channels` stores the number of values per pixel, what the PAM specification calls `depth`
- `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise)
- `scale` and `little_endian` are separated, so the `header` will always store a positive `scale`
- `little_endian` will only be true for a negative `scale` PFM, every other format will be false
- `little_endian` only describes the netpbm data being read/written, the image buffer will be native
*/
package netpbm
+24 -1
View File
@@ -29,7 +29,7 @@ Error :: enum i32 {
// Invalid_Write means that a write returned an impossible count
Invalid_Write,
// Short_Buffer means that a read required a longer buffer than was provided
// Short_Buffer means that a read/write required a longer buffer than was provided
Short_Buffer,
// No_Progress is returned by some implementations of `io.Reader` when many calls
@@ -359,6 +359,29 @@ read_at_least :: proc(r: Reader, buf: []byte, min: int) -> (n: int, err: Error)
return
}
// write_full writes until the entire contents of `buf` has been written or an error occurs.
write_full :: proc(w: Writer, buf: []byte) -> (n: int, err: Error) {
return write_at_least(w, buf, len(buf))
}
// write_at_least writes at least `buf[:min]` to the writer and returns the amount written.
// If an error occurs before writing everything it is returned.
write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error) {
if len(buf) < min {
return 0, .Short_Buffer
}
for n < min && err == nil {
nn: int
nn, err = write(w, buf[n:])
n += nn
}
if err == nil && n < min {
err = .Short_Write
}
return
}
// copy copies from src to dst till either EOF is reached on src or an error occurs
// It returns the number of bytes copied and the first error that occurred whilst copying, if any.
copy :: proc(dst: Writer, src: Reader) -> (written: i64, err: Error) {
+14
View File
@@ -1247,6 +1247,20 @@ internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags :=
a.digit[0] |= 3
}
if .Second_MSB_On in flags {
/*
Ensure there's enough space for the bit to be set.
*/
if a.used * _DIGIT_BITS < size_in_bits - 1 {
new_size := (size_in_bits - 1) / _DIGIT_BITS
if new_size % _DIGIT_BITS > 0 {
new_size += 1
}
internal_grow(a, new_size) or_return
a.used = new_size
}
internal_int_bitfield_set_single(a, size_in_bits - 2) or_return
}
+2 -2
View File
@@ -789,8 +789,8 @@ shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) {
return
}
for i := i64(0); i < n; i += 1 {
j := int63_max(n, r)
for i := i64(n - 1); i > 0; i -= 1 {
j := int63_max(i + 1, r)
array[i], array[j] = array[j], array[i]
}
}
+6 -2
View File
@@ -258,8 +258,12 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {})
if errno != .NONE {
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
if errno == .EPIPE {
// If the peer is disconnected when we are trying to send we will get an `EPIPE` error,
// so we turn that into a clearer error
return total_written, TCP_Send_Error.Connection_Closed
} else if errno != .NONE {
return total_written, TCP_Send_Error(errno)
}
total_written += int(res)
+21
View File
@@ -934,6 +934,27 @@ set_union_value :: proc(dst: any, value: any) -> bool {
panic("expected a union to reflect.set_union_variant_typeid")
}
@(require_results)
bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool {
if value == nil { return ODIN_ENDIAN == .Big }
ti := runtime.type_info_base(type_info_of(value.id))
if info, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok {
if info.underlying == nil { return ODIN_ENDIAN == .Big }
underlying_ti := runtime.type_info_base(info.underlying)
if underlying_info, uok := underlying_ti.variant.(runtime.Type_Info_Integer); uok {
switch underlying_info.endianness {
case .Platform: return ODIN_ENDIAN == .Big
case .Little: return false
case .Big: return true
}
}
return ODIN_ENDIAN == .Big
}
panic("expected a bit_set to reflect.bit_set_is_big_endian", loc)
}
@(require_results)
+64 -64
View File
@@ -1,78 +1,78 @@
/*
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
Package `core:sys/info` gathers system information on:
Windows, Linux, macOS, FreeBSD & OpenBSD.
Package `core:sys/info` gathers system information on:
Windows, Linux, macOS, FreeBSD & OpenBSD.
Simply import the package and you'll have access to the OS version, RAM amount
and CPU information.
Simply import the package and you'll have access to the OS version, RAM amount
and CPU information.
On Windows, GPUs will also be enumerated using the registry.
On Windows, GPUs will also be enumerated using the registry.
CPU feature flags can be tested against `cpu_features`, where applicable, e.g.
`if .aes in si.aes { ... }`
*/
//+build ignore
package sysinfo
CPU feature flags can be tested against `cpu_features`, where applicable, e.g.
`if .aes in si.aes { ... }`
import "core:fmt"
import si "core:sys/info"
Example:
main :: proc() {
fmt.printf("Odin: %v\n", ODIN_VERSION)
fmt.printf("OS: %v\n", si.os_version.as_string)
fmt.printf("OS: %#v\n", si.os_version)
fmt.printf("CPU: %v\n", si.cpu_name)
fmt.printf("RAM: %v MiB\n", si.ram.total_ram / 1024 / 1024)
import "core:fmt"
import si "core:sys/info"
fmt.println()
for gpu, i in si.gpus {
fmt.printf("GPU #%v:\n", i)
fmt.printf("\tVendor: %v\n", gpu.vendor_name)
fmt.printf("\tModel: %v\n", gpu.model_name)
fmt.printf("\tVRAM: %v MiB\n", gpu.total_ram / 1024 / 1024)
main :: proc() {
fmt.printf("Odin: %v\n", ODIN_VERSION)
fmt.printf("OS: %v\n", si.os_version.as_string)
fmt.printf("OS: %#v\n", si.os_version)
fmt.printf("CPU: %v\n", si.cpu_name)
fmt.printf("RAM: %v MiB\n", si.ram.total_ram / 1024 / 1024)
fmt.println()
for gpu, i in si.gpus {
fmt.printf("GPU #%v:\n", i)
fmt.printf("\tVendor: %v\n", gpu.vendor_name)
fmt.printf("\tModel: %v\n", gpu.model_name)
fmt.printf("\tVRAM: %v MiB\n", gpu.total_ram / 1024 / 1024)
}
}
}
/*
Example Windows output:
Odin: dev-2022-09
OS: Windows 10 Professional (version: 20H2), build: 19042.1466
OS: OS_Version{
platform = "Windows",
major = 10,
minor = 0,
- Example Windows output:
Odin: dev-2022-09
OS: Windows 10 Professional (version: 20H2), build: 19042.1466
OS: OS_Version{
platform = "Windows",
major = 10,
minor = 0,
patch = 0,
build = [
19042,
1466,
],
version = "20H2",
as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466",
}
CPU: AMD Ryzen 7 1800X Eight-Core Processor
RAM: 65469 MiB
GPU #0:
Vendor: Advanced Micro Devices, Inc.
Model: Radeon RX Vega
VRAM: 8176 MiB
- Example macOS output:
ODIN: dev-2022-09
OS: OS_Version{
platform = "MacOS",
major = 21,
minor = 5,
patch = 0,
build = [
19042,
1466,
0,
0,
],
version = "20H2",
as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466",
}
CPU: AMD Ryzen 7 1800X Eight-Core Processor
RAM: 65469 MiB
GPU #0:
Vendor: Advanced Micro Devices, Inc.
Model: Radeon RX Vega
VRAM: 8176 MiB
Example macOS output:
ODIN: dev-2022-09
OS: OS_Version{
platform = "MacOS",
major = 21,
minor = 5,
patch = 0,
build = [
0,
0,
],
version = "21F79",
as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)",
}
CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
RAM: 8192 MiB
version = "21F79",
as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)",
}
CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
RAM: 8192 MiB
*/
package sysinfo
+6 -6
View File
@@ -40,10 +40,10 @@ write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) {
*/
open :: proc "contextless" (name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) {
when ODIN_ARCH == .arm64 {
ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) mode)
ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode)
return errno_unwrap(ret, Fd)
} else {
ret := syscall(SYS_open, transmute(uintptr) name, transmute(u32) mode)
ret := syscall(SYS_open, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode)
return errno_unwrap(ret, Fd)
}
}
@@ -91,10 +91,10 @@ stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) {
*/
fstat :: proc "contextless" (fd: Fd, stat: ^Stat) -> (Errno) {
when size_of(int) == 8 {
ret := syscall(SYS_fstat, stat)
ret := syscall(SYS_fstat, cast(i32) fd, stat)
return Errno(-ret)
} else {
ret := syscall(SYS_fstat64, stat)
ret := syscall(SYS_fstat64, cast(i32) fd, stat)
return Errno(-ret)
}
}
@@ -787,8 +787,8 @@ exit :: proc "contextless" (code: i32) -> ! {
Wait for the process to change state.
Available since Linux 1.0.
*/
wait4 :: proc "contextless" (pid: Pid, status: ^u32, options: Wait_Options) -> (Pid, Errno) {
ret := syscall(SYS_wait4, pid, status, transmute(u32) options)
wait4 :: proc "contextless" (pid: Pid, status: ^u32, options: Wait_Options, rusage: ^RUsage) -> (Pid, Errno) {
ret := syscall(SYS_wait4, pid, status, transmute(u32) options, rusage)
return errno_unwrap(ret, Pid)
}
+21 -8
View File
@@ -137,6 +137,9 @@ clear_all :: proc(s: ^State) -> (cleared: bool) {
// push current text state to the wanted undo|redo stack
undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator_Error {
if s.builder != nil {
return nil
}
text := string(s.builder.buf[:])
item := (^Undo_State)(mem.alloc(size_of(Undo_State) + len(text), align_of(Undo_State), s.undo_text_allocator) or_return)
item.selection = s.selection
@@ -154,7 +157,7 @@ undo :: proc(s: ^State, undo, redo: ^[dynamic]^Undo_State) {
undo_state_push(s, redo)
item := pop(undo)
s.selection = item.selection
#no_bounds_check {
#no_bounds_check if s.builder != nil {
strings.builder_reset(s.builder)
strings.write_string(s.builder, string(item.text[:item.len]))
}
@@ -224,13 +227,17 @@ input_rune :: proc(s: ^State, r: rune) {
// insert a single rune into the edit state - deletes the current selection
insert :: proc(s: ^State, at: int, text: string) {
undo_check(s)
inject_at(&s.builder.buf, at, text)
if s.builder != nil {
inject_at(&s.builder.buf, at, text)
}
}
// remove the wanted range withing, usually the selection within byte indices
remove :: proc(s: ^State, lo, hi: int) {
undo_check(s)
remove_range(&s.builder.buf, lo, hi)
if s.builder != nil {
remove_range(&s.builder.buf, lo, hi)
}
}
// true if selection head and tail dont match and form a selection of multiple characters
@@ -244,8 +251,8 @@ has_selection :: proc(s: ^State) -> bool {
sorted_selection :: proc(s: ^State) -> (lo, hi: int) {
lo = min(s.selection[0], s.selection[1])
hi = max(s.selection[0], s.selection[1])
lo = clamp(lo, 0, len(s.builder.buf))
hi = clamp(hi, 0, len(s.builder.buf))
lo = clamp(lo, 0, len(s.builder.buf) if s.builder != nil else 0)
hi = clamp(hi, 0, len(s.builder.buf) if s.builder != nil else 0)
return
}
@@ -265,7 +272,10 @@ translate_position :: proc(s: ^State, t: Translation) -> int {
return b == ' ' || b == '\t' || b == '\n'
}
buf := s.builder.buf[:]
buf: []byte
if s.builder != nil {
buf = s.builder.buf[:]
}
pos := clamp(s.selection[0], 0, len(buf))
switch t {
@@ -352,7 +362,10 @@ delete_to :: proc(s: ^State, t: Translation) {
// return the currently selected text
current_selected_text :: proc(s: ^State) -> string {
lo, hi := sorted_selection(s)
return string(s.builder.buf[lo:hi])
if s.builder != nil {
return string(s.builder.buf[lo:hi])
}
return ""
}
// copy & delete the current selection when copy() succeeds
@@ -431,7 +444,7 @@ perform_command :: proc(s: ^State, cmd: Command) {
case .Cut: cut(s)
case .Copy: copy(s)
case .Paste: paste(s)
case .Select_All: s.selection = {len(s.builder.buf), 0}
case .Select_All: s.selection = {len(s.builder.buf) if s.builder != nil else 0, 0}
case .Backspace: delete_to(s, .Left)
case .Delete: delete_to(s, .Right)
case .Delete_Word_Left: delete_to(s, .Word_Left)
+83 -88
View File
@@ -1,111 +1,106 @@
//+build ignore
package i18n
/*
The i18n package is flexible and easy to use.
The `i18n` package is flexible and easy to use.
It has one call to get a translation: `get`, which the user can alias into something like `T`.
It has one call to get a translation: `get`, which the user can alias into something like `T`.
`get`, referred to as `T` here, has a few different signatures.
All of them will return the key if the entry can't be found in the active translation catalog.
`get`, referred to as `T` here, has a few different signatures.
All of them will return the key if the entry can't be found in the active translation catalog.
- `T(key)` returns the translation of `key`.
- `T(key, n)` returns a pluralized translation of `key` according to value `n`.
- `T(key)` returns the translation of `key`.
- `T(key, n)` returns a pluralized translation of `key` according to value `n`.
- `T(section, key)` returns the translation of `key` in `section`.
- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`.
- `T(section, key)` returns the translation of `key` in `section`.
- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`.
By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use.
If you want to override which translation to use, for example in a language preview dialog, you can use the following:
By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use.
If you want to override which translation to use, for example in a language preview dialog, you can use the following:
- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog.
- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog.
- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog.
- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog.
If a catalog has translation contexts or sections, then ommitting it in the above calls looks up in section "".
If a catalog has translation contexts or sections, then omitting it in the above calls looks up in section "".
The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form.
Passing n != 1 returns plural form 1.
The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form.
Passing n != 1 returns plural form 1.
Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser.
This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used.
Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser.
This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used.
You can also assign it to a loaded catalog after parsing, of course.
You can also assign it to a loaded catalog after parsing, of course.
Some code examples follow.
*/
Example:
/*
```cpp
import "core:fmt"
import "core:text/i18n"
import "core:fmt"
import "core:text/i18n"
T :: i18n.get
T :: i18n.get
mo :: proc() {
using fmt
mo :: proc() {
using fmt
err: i18n.Error
err: i18n.Error
/*
Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
*/
i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo"))
defer i18n.destroy()
/*
Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
*/
i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo"))
defer i18n.destroy()
if err != .None { return }
if err != .None { return }
/*
These are in the .MO catalog.
*/
println("-----")
println(T(""))
println("-----")
println(T("There are 69,105 leaves here."))
println("-----")
println(T("Hellope, World!"))
println("-----")
// We pass 1 into `T` to get the singular format string, then 1 again into printf.
printf(T("There is %d leaf.\n", 1), 1)
// We pass 42 into `T` to get the plural format string, then 42 again into printf.
printf(T("There is %d leaf.\n", 42), 42)
/*
These are in the .MO catalog.
*/
println("-----")
println(T(""))
println("-----")
println(T("There are 69,105 leaves here."))
println("-----")
println(T("Hellope, World!"))
println("-----")
// We pass 1 into `T` to get the singular format string, then 1 again into printf.
printf(T("There is %d leaf.\n", 1), 1)
// We pass 42 into `T` to get the plural format string, then 42 again into printf.
printf(T("There is %d leaf.\n", 42), 42)
/*
This isn't in the translation catalog, so the key is passed back untranslated.
*/
println("-----")
println(T("Come visit us on Discord!"))
}
qt :: proc() {
using fmt
err: i18n.Error
/*
Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
*/
i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts"))
defer i18n.destroy()
if err != .None {
return
/*
This isn't in the translation catalog, so the key is passed back untranslated.
*/
println("-----")
println(T("Come visit us on Discord!"))
}
/*
These are in the .TS catalog. As you can see they have sections.
*/
println("--- Page section ---")
println("Page:Text for translation =", T("Page", "Text for translation"))
println("-----")
println("Page:Also text to translate =", T("Page", "Also text to translate"))
println("-----")
println("--- installscript section ---")
println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall"))
println("-----")
println("--- apple_count section ---")
println("apple_count:%d apple(s) =")
println("\t 1 =", T("apple_count", "%d apple(s)", 1))
println("\t 42 =", T("apple_count", "%d apple(s)", 42))
}
```
*/
qt :: proc() {
using fmt
err: i18n.Error
/*
Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
*/
i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts"))
defer i18n.destroy()
if err != .None {
return
}
/*
These are in the .TS catalog. As you can see they have sections.
*/
println("--- Page section ---")
println("Page:Text for translation =", T("Page", "Text for translation"))
println("-----")
println("Page:Also text to translate =", T("Page", "Also text to translate"))
println("-----")
println("--- installscript section ---")
println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall"))
println("-----")
println("--- apple_count section ---")
println("apple_count:%d apple(s) =")
println("\t 1 =", T("apple_count", "%d apple(s)", 1))
println("\t 42 =", T("apple_count", "%d apple(s)", 42))
}
*/
package i18n
+2 -2
View File
@@ -90,7 +90,7 @@ DEFAULT_PARSE_OPTIONS :: Parse_Options{
- get(key, number), which returns the appropriate plural from the active catalog, or
- get(key, number, catalog) to grab text from a specific one.
*/
get_single_section :: proc(key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
get_single_section :: proc(key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) {
/*
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
*/
@@ -108,7 +108,7 @@ get_single_section :: proc(key: string, number := 0, catalog: ^Translation = ACT
- get(section, key, number), which returns the appropriate plural from the active catalog, or
- get(section, key, number, catalog) to grab text from a specific one.
*/
get_by_section :: proc(section, key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
get_by_section :: proc(section, key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) {
/*
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
*/
+9 -22
View File
@@ -1,11 +1,8 @@
/*
package table implements ascii/markdown/html/custom rendering of tables.
The package `table` implements ASCII/markdown/HTML/custom rendering of tables.
---
**Custom rendering example:**
Custom rendering example:
```odin
tbl := init(&Table{})
padding(tbl, 0, 1)
row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM")
@@ -17,19 +14,14 @@
}
io.write_byte(stdio_writer(), '\n')
}
```
This outputs:
```
This outputs:
A_LONG_ENUM = 54, // A comment about A_LONG_ENUM
AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM
```
---
**ASCII rendering example:**
ASCII rendering example:
```odin
tbl := init(&Table{})
defer destroy(tbl)
@@ -69,10 +61,9 @@
write_ascii_table(stdio_writer(), tbl)
write_markdown_table(stdio_writer(), tbl)
```
This outputs:
```
This outputs:
+-----------------------------------------------+
| This is a table caption and it is very long |
+------------------+-----------------+----------+
@@ -82,19 +73,15 @@
| 000000005 | 6.283185 | |
| a | bbb | c |
+------------------+-----------------+----------+
```
and
and
```
| AAAAAAAAA | B | C |
|:-----------------|:---------------:|---------:|
| 123 | foo | |
| 000000005 | 6.283185 | |
| a | bbb | c |
```
respectively.
respectively.
*/
package text_table
+2
View File
@@ -59,6 +59,7 @@ import json "core:encoding/json"
import varint "core:encoding/varint"
import xml "core:encoding/xml"
import endian "core:encoding/endian"
import cbor "core:encoding/cbor"
import fmt "core:fmt"
import hash "core:hash"
@@ -180,6 +181,7 @@ _ :: json
_ :: varint
_ :: xml
_ :: endian
_ :: cbor
_ :: fmt
_ :: hash
_ :: xxhash
+3 -4
View File
@@ -382,6 +382,7 @@ struct BuildContext {
bool keep_temp_files;
bool ignore_unknown_attributes;
bool no_bounds_check;
bool no_type_assert;
bool no_dynamic_literals;
bool no_output_files;
bool no_crt;
@@ -839,13 +840,11 @@ gb_internal String odin_root_dir(void) {
char const *found = gb_get_env("ODIN_ROOT", a);
if (found) {
String path = path_to_full_path(a, make_string_c(found));
if (path[path.len-1] != '/' && path[path.len-1] != '\\') {
#if defined(GB_SYSTEM_WINDOWS)
path = concatenate_strings(a, path, WIN32_SEPARATOR_STRING);
path = normalize_path(a, path, WIN32_SEPARATOR_STRING);
#else
path = concatenate_strings(a, path, NIX_SEPARATOR_STRING);
path = normalize_path(a, path, NIX_SEPARATOR_STRING);
#endif
}
global_module_path = path;
global_module_path_set = true;
+22 -11
View File
@@ -29,10 +29,11 @@ gb_internal void populate_using_array_index(CheckerContext *ctx, Ast *node, AstF
}
}
gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, AstField *field, Type *t) {
gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, AstField *field, Type *t, isize level) {
if (t == nullptr) {
return;
}
Type *original_type = t;
t = base_type(type_deref(t));
gbString str = nullptr;
defer (gb_string_free(str));
@@ -46,16 +47,18 @@ gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, Ast
String name = f->token.string;
Entity *e = scope_lookup_current(ctx->scope, name);
if (e != nullptr && name != "_") {
gbString ot = type_to_string(original_type);
// TODO(bill): Better type error
if (str != nullptr) {
error(e->token, "'%.*s' is already declared in '%s'", LIT(name), str);
error(e->token, "'%.*s' is already declared in '%s', through 'using' from '%s'", LIT(name), str, ot);
} else {
error(e->token, "'%.*s' is already declared", LIT(name));
error(e->token, "'%.*s' is already declared, through 'using' from '%s'", LIT(name), ot);
}
gb_string_free(ot);
} else {
add_entity(ctx, ctx->scope, nullptr, f);
if (f->flags & EntityFlag_Using) {
populate_using_entity_scope(ctx, node, field, f->type);
populate_using_entity_scope(ctx, node, field, f->type, level+1);
}
}
}
@@ -200,7 +203,7 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entit
continue;
}
populate_using_entity_scope(ctx, node, p, type);
populate_using_entity_scope(ctx, node, p, type, 1);
}
if (is_subtype && p->names.count > 0) {
@@ -2495,18 +2498,16 @@ gb_internal Type *get_map_cell_type(Type *type) {
return s;
}
gb_internal void init_map_internal_types(Type *type) {
gb_internal void init_map_internal_debug_types(Type *type) {
GB_ASSERT(type->kind == Type_Map);
GB_ASSERT(t_allocator != nullptr);
if (type->Map.lookup_result_type != nullptr) return;
if (type->Map.debug_metadata_type != nullptr) return;
Type *key = type->Map.key;
Type *value = type->Map.value;
GB_ASSERT(key != nullptr);
GB_ASSERT(value != nullptr);
Type *key_cell = get_map_cell_type(key);
Type *value_cell = get_map_cell_type(value);
@@ -2541,6 +2542,18 @@ gb_internal void init_map_internal_types(Type *type) {
gb_unused(type_size_of(debug_type));
type->Map.debug_metadata_type = debug_type;
}
gb_internal void init_map_internal_types(Type *type) {
GB_ASSERT(type->kind == Type_Map);
GB_ASSERT(t_allocator != nullptr);
if (type->Map.lookup_result_type != nullptr) return;
Type *key = type->Map.key;
Type *value = type->Map.value;
GB_ASSERT(key != nullptr);
GB_ASSERT(value != nullptr);
type->Map.lookup_result_type = make_optional_ok_type(value);
}
@@ -2613,8 +2626,6 @@ gb_internal void check_map_type(CheckerContext *ctx, Type *type, Ast *node) {
init_core_map_type(ctx->checker);
init_map_internal_types(type);
// error(node, "'map' types are not yet implemented");
}
gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) {
+2
View File
@@ -1111,6 +1111,8 @@ gb_internal void init_universal(void) {
add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG);
add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT);
add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR);
add_global_bool_constant("ODIN_NO_BOUNDS_CHECK", build_context.no_bounds_check);
add_global_bool_constant("ODIN_NO_TYPE_ASSERT", build_context.no_type_assert);
add_global_bool_constant("ODIN_DEFAULT_TO_PANIC_ALLOCATOR", bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR);
add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals);
add_global_bool_constant("ODIN_NO_CRT", bc->no_crt);
+4 -1
View File
@@ -563,4 +563,7 @@ gb_internal void init_mem_allocator(Checker *c);
gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped);
gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type);
gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type);
gb_internal void init_map_internal_types(Type *type);
+88 -65
View File
@@ -7,7 +7,8 @@ struct ErrorValue {
ErrorValueKind kind;
TokenPos pos;
TokenPos end;
Array<String> msgs;
Array<u8> msg;
bool seen_newline;
};
struct ErrorCollector {
@@ -30,19 +31,21 @@ gb_global ErrorCollector global_error_collector;
gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) {
GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue");
ErrorValue ev = {kind, pos};
ev.msgs.allocator = heap_allocator();
ev.msg.allocator = heap_allocator();
global_error_collector.curr_error_value = ev;
global_error_collector.curr_error_value_set.store(true);
}
gb_internal void pop_error_value(void) {
mutex_lock(&global_error_collector.mutex);
if (global_error_collector.curr_error_value_set.load()) {
array_add(&global_error_collector.error_values, global_error_collector.curr_error_value);
global_error_collector.curr_error_value = {};
global_error_collector.curr_error_value_set.store(false);
}
mutex_unlock(&global_error_collector.mutex);
}
@@ -180,9 +183,18 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) {
isize n = len-1;
if (n > 0) {
String msg = copy_string(permanent_allocator(), {(u8 *)buf, n});
ErrorValue *ev = get_error_value();
array_add(&ev->msgs, msg);
if (terse_errors()) {
for (isize i = 0; i < n && !ev->seen_newline; i++) {
u8 c = cast(u8)buf[i];
if (c == '\n') {
ev->seen_newline = true;
}
array_add(&ev->msg, c);
}
} else {
array_add_elems(&ev->msg, (u8 *)buf, n);
}
}
}
@@ -292,10 +304,11 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con
if (line_len > MAX_LINE_LENGTH_PADDED) {
i32 left = MAX_TAB_WIDTH;
if (offset > 0) {
line_text += offset-left;
line_len -= offset-left;
offset = left+MAX_TAB_WIDTH/2;
i32 diff = gb_max(offset-left, 0);
if (diff > 0) {
line_text += diff;
line_len -= diff;
offset = left + ELLIPSIS_PADDING/2;
}
if (line_len > MAX_LINE_LENGTH_PADDED) {
line_len = MAX_LINE_LENGTH_PADDED;
@@ -304,7 +317,7 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con
squiggle_extra = 1;
}
}
if (offset > 0) {
if (diff > 0) {
error_out("... %.*s ...", cast(i32)line_len, line_text);
} else {
error_out("%.*s ...", cast(i32)line_len, line_text);
@@ -644,109 +657,119 @@ gb_internal int error_value_cmp(void const *a, void const *b) {
}
gb_internal void print_all_errors(void) {
auto const &escape_char = [](gbFile *f, u8 c) {
auto const &escape_char = [](gbString res, u8 c) -> gbString {
switch (c) {
case '\n': gb_file_write(f, "\\n", 2); break;
case '"': gb_file_write(f, "\\\"", 2); break;
case '\\': gb_file_write(f, "\\\\", 2); break;
case '\b': gb_file_write(f, "\\b", 2); break;
case '\f': gb_file_write(f, "\\f", 2); break;
case '\r': gb_file_write(f, "\\r", 2); break;
case '\t': gb_file_write(f, "\\t", 2); break;
case '\n': res = gb_string_append_length(res, "\\n", 2); break;
case '"': res = gb_string_append_length(res, "\\\"", 2); break;
case '\\': res = gb_string_append_length(res, "\\\\", 2); break;
case '\b': res = gb_string_append_length(res, "\\b", 2); break;
case '\f': res = gb_string_append_length(res, "\\f", 2); break;
case '\r': res = gb_string_append_length(res, "\\r", 2); break;
case '\t': res = gb_string_append_length(res, "\\t", 2); break;
default:
if ('\x00' <= c && c <= '\x1f') {
gb_fprintf(f, "\\u%04x", c);
res = gb_string_append_fmt(res, "\\u%04x", c);
} else {
gb_file_write(f, &c, 1);
res = gb_string_append_length(res, &c, 1);
}
break;
}
return res;
};
GB_ASSERT(any_errors() || any_warnings());
gbFile *f = gb_file_get_standard(gbFileStandard_Error);
array_sort(global_error_collector.error_values, error_value_cmp);
gbString res = gb_string_make(heap_allocator(), "");
defer (gb_string_free(res));
if (json_errors()) {
gb_fprintf(f, "{\n");
gb_fprintf(f, "\t\"error_count\": %td,\n", global_error_collector.error_values.count);
gb_fprintf(f, "\t\"errors\": [\n");
res = gb_string_append_fmt(res, "{\n");
res = gb_string_append_fmt(res, "\t\"error_count\": %td,\n", global_error_collector.error_values.count);
res = gb_string_append_fmt(res, "\t\"errors\": [\n");
for_array(i, global_error_collector.error_values) {
ErrorValue ev = global_error_collector.error_values[i];
gb_fprintf(f, "\t\t{\n");
res = gb_string_append_fmt(res, "\t\t{\n");
gb_fprintf(f, "\t\t\t\"type\": \"");
res = gb_string_append_fmt(res, "\t\t\t\"type\": \"");
if (ev.kind == ErrorValue_Warning) {
gb_fprintf(f, "warning");
res = gb_string_append_fmt(res, "warning");
} else {
gb_fprintf(f, "error");
res = gb_string_append_fmt(res, "error");
}
gb_fprintf(f, "\",\n");
res = gb_string_append_fmt(res, "\",\n");
gb_fprintf(f, "\t\t\t\"pos\": {\n");
res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n");
if (ev.pos.file_id) {
gb_fprintf(f, "\t\t\t\t\"file\": \"");
res = gb_string_append_fmt(res, "\t\t\t\t\"file\": \"");
String file = get_file_path_string(ev.pos.file_id);
for (isize k = 0; k < file.len; k++) {
escape_char(f, file.text[k]);
res = escape_char(res, file.text[k]);
}
gb_fprintf(f, "\",\n");
gb_fprintf(f, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset);
gb_fprintf(f, "\t\t\t\t\"line\": %d,\n", ev.pos.line);
gb_fprintf(f, "\t\t\t\t\"column\": %d,\n", ev.pos.column);
res = gb_string_append_fmt(res, "\",\n");
res = gb_string_append_fmt(res, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset);
res = gb_string_append_fmt(res, "\t\t\t\t\"line\": %d,\n", ev.pos.line);
res = gb_string_append_fmt(res, "\t\t\t\t\"column\": %d,\n", ev.pos.column);
i32 end_column = gb_max(ev.end.column, ev.pos.column);
gb_fprintf(f, "\t\t\t\t\"end_column\": %d\n", end_column);
gb_fprintf(f, "\t\t\t},\n");
res = gb_string_append_fmt(res, "\t\t\t\t\"end_column\": %d\n", end_column);
res = gb_string_append_fmt(res, "\t\t\t},\n");
}
gb_fprintf(f, "\t\t\t\"msgs\": [\n");
res = gb_string_append_fmt(res, "\t\t\t\"msgs\": [\n");
if (ev.msgs.count > 1) {
gb_fprintf(f, "\t\t\t\t\"");
auto lines = split_lines_from_array(ev.msg, heap_allocator());
defer (array_free(&lines));
for (isize j = 1; j < ev.msgs.count; j++) {
String msg = ev.msgs[j];
for (isize k = 0; k < msg.len; k++) {
u8 c = msg.text[k];
if (c == '\n') {
if (k+1 == msg.len && j+1 == ev.msgs.count) {
// don't do the last one
} else {
gb_fprintf(f, "\",\n");
gb_fprintf(f, "\t\t\t\t\"");
}
} else {
escape_char(f, c);
}
if (lines.count > 0) {
res = gb_string_append_fmt(res, "\t\t\t\t\"");
for (isize j = 0; j < lines.count; j++) {
String line = lines[j];
for (isize k = 0; k < line.len; k++) {
u8 c = line.text[k];
res = escape_char(res, c);
}
if (j+1 < lines.count) {
res = gb_string_append_fmt(res, "\",\n");
res = gb_string_append_fmt(res, "\t\t\t\t\"");
}
}
gb_fprintf(f, "\"\n");
res = gb_string_append_fmt(res, "\"\n");
}
gb_fprintf(f, "\t\t\t]\n");
gb_fprintf(f, "\t\t}");
res = gb_string_append_fmt(res, "\t\t\t]\n");
res = gb_string_append_fmt(res, "\t\t}");
if (i+1 != global_error_collector.error_values.count) {
gb_fprintf(f, ",");
res = gb_string_append_fmt(res, ",");
}
gb_fprintf(f, "\n");
res = gb_string_append_fmt(res, "\n");
}
gb_fprintf(f, "\t]\n");
gb_fprintf(f, "}\n");
res = gb_string_append_fmt(res, "\t]\n");
res = gb_string_append_fmt(res, "}\n");
} else {
for_array(i, global_error_collector.error_values) {
ErrorValue ev = global_error_collector.error_values[i];
for (isize j = 0; j < ev.msgs.count; j++) {
String msg = ev.msgs[j];
gb_file_write(f, msg.text, msg.len);
if (terse_errors() && string_contains_char(msg, '\n')) {
String_Iterator it = {{ev.msg.data, ev.msg.count}, 0};
for (isize line_idx = 0; /**/; line_idx++) {
String line = string_split_iterator(&it, '\n');
if (line.len == 0) {
break;
}
line = string_trim_trailing_whitespace(line);
res = gb_string_append_length(res, line.text, line.len);
res = gb_string_append_length(res, " \n", 2);
if (line_idx == 0 && terse_errors()) {
break;
}
}
}
}
gbFile *f = gb_file_get_standard(gbFileStandard_Error);
gb_file_write(f, res, gb_string_length(res));
}
+1 -1
View File
@@ -2659,7 +2659,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
LLVMSetInitializer(g, LLVMConstNull(internal_llvm_type));
LLVMSetLinkage(g, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage);
LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr);
LLVMSetGlobalConstant(g, /*true*/false);
LLVMSetGlobalConstant(g, true);
lbValue value = {};
value.value = g;
+4 -10
View File
@@ -739,6 +739,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) {
}
case Type_Map: {
init_map_internal_debug_types(type);
Type *bt = base_type(type->Map.debug_metadata_type);
GB_ASSERT(bt->kind == Type_Struct);
@@ -945,6 +946,7 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) {
}
case Type_Map: {
init_map_internal_debug_types(bt);
bt = base_type(bt->Map.debug_metadata_type);
GB_ASSERT(bt->kind == Type_Struct);
return lb_debug_struct(m, type, bt, name, scope, file, line);
@@ -1025,7 +1027,7 @@ gb_internal void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, T
LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block);
}
gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block, lbArgKind arg_kind) {
gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block) {
if (p->debug_info == nullptr) {
return;
}
@@ -1086,15 +1088,7 @@ gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, T
// NOTE(bill, 2022-02-01): For parameter values, you must insert them at the end of the decl block
// The reason is that if the parameter is at index 0 and a pointer, there is not such things as an
// instruction "before" it.
switch (arg_kind) {
case lbArg_Direct:
LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block);
break;
case lbArg_Indirect:
LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block);
break;
}
LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block);
}
+2 -2
View File
@@ -3116,7 +3116,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) {
Type *dst_type = type;
if ((p->state_flags & StateFlag_no_type_assert) == 0) {
if (!build_context.no_type_assert && (p->state_flags & StateFlag_no_type_assert) == 0) {
lbValue src_tag = {};
lbValue dst_tag = {};
if (is_type_union_maybe_pointer(src_type)) {
@@ -3156,7 +3156,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) {
v = lb_emit_load(p, v);
}
lbValue data_ptr = lb_emit_struct_ev(p, v, 0);
if ((p->state_flags & StateFlag_no_type_assert) == 0) {
if (!build_context.no_type_assert && (p->state_flags & StateFlag_no_type_assert) == 0) {
GB_ASSERT(!build_context.no_rtti);
lbValue any_id = lb_emit_struct_ev(p, v, 1);
+2 -2
View File
@@ -211,7 +211,7 @@ gb_internal void lb_loop_end(lbProcedure *p, lbLoopData const &data) {
gb_internal void lb_make_global_private_const(LLVMValueRef global_data) {
LLVMSetLinkage(global_data, LLVMPrivateLinkage);
LLVMSetLinkage(global_data, LLVMLinkerPrivateLinkage);
LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr);
LLVMSetGlobalConstant(global_data, true);
}
@@ -2070,7 +2070,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
break;
case Type_Map:
init_map_internal_types(type);
init_map_internal_debug_types(type);
GB_ASSERT(t_raw_map != nullptr);
return lb_type_internal(m, t_raw_map);
+2 -11
View File
@@ -597,16 +597,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) {
lbValue ptr = lb_address_from_load_or_generate_local(p, param);
GB_ASSERT(LLVMIsAAllocaInst(ptr.value));
lb_add_entity(p->module, e, ptr);
lbBlock *block = p->decl_block;
if (original_value != value) {
block = p->curr_block;
}
LLVMValueRef debug_storage_value = value;
if (original_value != value && LLVMIsALoadInst(value)) {
debug_storage_value = LLVMGetOperand(value, 0);
}
lb_add_debug_param_variable(p, debug_storage_value, e->type, e->token, param_index+1, block, arg_type->kind);
lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->curr_block);
}
} else if (arg_type->kind == lbArg_Indirect) {
if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) {
@@ -614,7 +605,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) {
ptr.value = LLVMGetParam(p->value, param_offset+param_index);
ptr.type = alloc_type_pointer(e->type);
lb_add_entity(p->module, e, ptr);
lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block, arg_type->kind);
lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block);
}
}
}
+6 -4
View File
@@ -249,9 +249,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
char name[64] = {};
gb_snprintf(name, 63, "__$ti-%lld", cast(long long)index);
LLVMValueRef g = LLVMAddGlobal(m->mod, type, name);
LLVMSetLinkage(g, LLVMInternalLinkage);
LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr);
LLVMSetGlobalConstant(g, true);
lb_make_global_private_const(g);
return g;
};
@@ -903,7 +901,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
case Type_Map: {
tag_type = t_type_info_map;
init_map_internal_types(t);
init_map_internal_debug_types(t);
LLVMValueRef vals[3] = {
get_type_info_ptr(m, t->Map.key),
@@ -1103,6 +1101,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
LLVMValueRef giant_const = LLVMConstArray(lb_type(m, t_type_info_ptr), giant_const_values, cast(unsigned)global_type_info_data_entity_count);
LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value;
LLVMSetInitializer(giant_array, giant_const);
lb_make_global_private_const(giant_array);
}
@@ -1132,4 +1131,7 @@ gb_internal void lb_setup_type_info_data(lbModule *m) { // NOTE(bill): Setup typ
LLVMValueRef slice = llvm_const_slice_internal(m, data, len);
LLVMSetInitializer(global_type_table.value, slice);
// force it to be constant
LLVMSetGlobalConstant(global_type_table.value, true);
}
+44 -40
View File
@@ -728,30 +728,32 @@ gb_internal lbValue lb_emit_union_cast(lbProcedure *p, lbValue value, Type *type
lb_start_block(p, end_block);
if (!is_tuple) {
GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0);
// NOTE(bill): Panic on invalid conversion
Type *dst_type = tuple->Tuple.variables[0]->type;
if (!build_context.no_type_assert) {
GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0);
// NOTE(bill): Panic on invalid conversion
Type *dst_type = tuple->Tuple.variables[0]->type;
isize arg_count = 7;
if (build_context.no_rtti) {
arg_count = 4;
isize arg_count = 7;
if (build_context.no_rtti) {
arg_count = 4;
}
lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1));
auto args = array_make<lbValue>(permanent_allocator(), arg_count);
args[0] = ok;
args[1] = lb_const_string(m, get_file_path_string(pos.file_id));
args[2] = lb_const_int(m, t_i32, pos.line);
args[3] = lb_const_int(m, t_i32, pos.column);
if (!build_context.no_rtti) {
args[4] = lb_typeid(m, src_type);
args[5] = lb_typeid(m, dst_type);
args[6] = lb_emit_conv(p, value_, t_rawptr);
}
lb_emit_runtime_call(p, "type_assertion_check2", args);
}
lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1));
auto args = array_make<lbValue>(permanent_allocator(), arg_count);
args[0] = ok;
args[1] = lb_const_string(m, get_file_path_string(pos.file_id));
args[2] = lb_const_int(m, t_i32, pos.line);
args[3] = lb_const_int(m, t_i32, pos.column);
if (!build_context.no_rtti) {
args[4] = lb_typeid(m, src_type);
args[5] = lb_typeid(m, dst_type);
args[6] = lb_emit_conv(p, value_, t_rawptr);
}
lb_emit_runtime_call(p, "type_assertion_check2", args);
return lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 0));
}
return lb_addr_load(p, v);
@@ -806,25 +808,27 @@ gb_internal lbAddr lb_emit_any_cast_addr(lbProcedure *p, lbValue value, Type *ty
if (!is_tuple) {
// NOTE(bill): Panic on invalid conversion
lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1));
if (!build_context.no_type_assert) {
lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1));
isize arg_count = 7;
if (build_context.no_rtti) {
arg_count = 4;
isize arg_count = 7;
if (build_context.no_rtti) {
arg_count = 4;
}
auto args = array_make<lbValue>(permanent_allocator(), arg_count);
args[0] = ok;
args[1] = lb_const_string(m, get_file_path_string(pos.file_id));
args[2] = lb_const_int(m, t_i32, pos.line);
args[3] = lb_const_int(m, t_i32, pos.column);
if (!build_context.no_rtti) {
args[4] = any_typeid;
args[5] = dst_typeid;
args[6] = lb_emit_struct_ev(p, value, 0);
}
lb_emit_runtime_call(p, "type_assertion_check2", args);
}
auto args = array_make<lbValue>(permanent_allocator(), arg_count);
args[0] = ok;
args[1] = lb_const_string(m, get_file_path_string(pos.file_id));
args[2] = lb_const_int(m, t_i32, pos.line);
args[3] = lb_const_int(m, t_i32, pos.column);
if (!build_context.no_rtti) {
args[4] = any_typeid;
args[5] = dst_typeid;
args[6] = lb_emit_struct_ev(p, value, 0);
}
lb_emit_runtime_call(p, "type_assertion_check2", args);
return lb_addr(lb_emit_struct_ep(p, v.addr, 0));
}
@@ -1125,7 +1129,7 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) {
case 3: result_type = t_allocator; break;
}
} else if (is_type_map(t)) {
init_map_internal_types(t);
init_map_internal_debug_types(t);
Type *itp = alloc_type_pointer(t_raw_map);
s = lb_emit_transmute(p, s, itp);
@@ -1264,7 +1268,7 @@ gb_internal lbValue lb_emit_struct_ev(lbProcedure *p, lbValue s, i32 index) {
case Type_Map:
{
init_map_internal_types(t);
init_map_internal_debug_types(t);
switch (index) {
case 0: result_type = get_struct_field_type(t_raw_map, 0); break;
case 1: result_type = get_struct_field_type(t_raw_map, 1); break;
+24 -6
View File
@@ -198,7 +198,12 @@ gb_internal void print_usage_line(i32 indent, char const *fmt, ...) {
gb_printf("\n");
}
gb_internal void usage(String argv0) {
gb_internal void usage(String argv0, String argv1 = {}) {
if (argv1 == "run.") {
print_usage_line(0, "Did you mean 'odin run .'?");
} else if (argv1 == "build.") {
print_usage_line(0, "Did you mean 'odin build .'?");
}
print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(argv0));
print_usage_line(0, "Usage:");
print_usage_line(1, "%.*s command [arguments]", LIT(argv0));
@@ -243,6 +248,7 @@ enum BuildFlagKind {
BuildFlag_Debug,
BuildFlag_DisableAssert,
BuildFlag_NoBoundsCheck,
BuildFlag_NoTypeAssert,
BuildFlag_NoDynamicLiterals,
BuildFlag_NoCRT,
BuildFlag_NoEntryPoint,
@@ -336,12 +342,12 @@ struct BuildFlag {
String name;
BuildFlagParamKind param_kind;
u32 command_support;
bool allow_mulitple;
bool allow_multiple;
};
gb_internal void add_flag(Array<BuildFlag> *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind, u32 command_support, bool allow_mulitple=false) {
BuildFlag flag = {kind, name, param_kind, command_support, allow_mulitple};
gb_internal void add_flag(Array<BuildFlag> *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind, u32 command_support, bool allow_multiple=false) {
BuildFlag flag = {kind, name, param_kind, command_support, allow_multiple};
array_add(build_flags, flag);
}
@@ -436,6 +442,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoTypeAssert, str_lit("no-type-assert"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoThreadLocal, str_lit("no-thread-local"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build);
@@ -1013,6 +1020,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
case BuildFlag_NoBoundsCheck:
build_context.no_bounds_check = true;
break;
case BuildFlag_NoTypeAssert:
build_context.no_type_assert = true;
break;
case BuildFlag_NoDynamicLiterals:
build_context.no_dynamic_literals = true;
break;
@@ -1353,7 +1363,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
}
}
if (!bf.allow_mulitple) {
if (!bf.allow_multiple) {
set_flags[bf.kind] = ok;
}
}
@@ -1850,6 +1860,10 @@ gb_internal void print_show_help(String const arg0, String const &command) {
print_usage_line(2, "Disables bounds checking program wide.");
print_usage_line(0, "");
print_usage_line(1, "-no-type-assert");
print_usage_line(2, "Disables type assertion checking program wide.");
print_usage_line(0, "");
print_usage_line(1, "-no-crt");
print_usage_line(2, "Disables automatic linking with the C Run Time.");
print_usage_line(0, "");
@@ -2577,7 +2591,11 @@ int main(int arg_count, char const **arg_ptr) {
gb_printf("%.*s", LIT(odin_root_dir()));
return 0;
} else {
usage(args[0]);
String argv1 = {};
if (args.count > 1) {
argv1 = args[1];
}
usage(args[0], argv1);
return 1;
}
+6
View File
@@ -6114,7 +6114,13 @@ gb_internal bool parse_file(Parser *p, AstFile *f) {
CommentGroup *docs = f->lead_comment;
if (f->curr_token.kind != Token_package) {
ERROR_BLOCK();
syntax_error(f->curr_token, "Expected a package declaration at the beginning of the file");
// IMPORTANT NOTE(bill): this is technically a race condition with the suggestion, but it's ony a suggession
// so in practice is should be "fine"
if (f->pkg && f->pkg->name != "") {
error_line("\tSuggestion: Add 'package %.*s' to the top of the file\n", LIT(f->pkg->name));
}
return false;
}
+68 -6
View File
@@ -171,6 +171,9 @@ template <isize N> gb_internal bool operator > (String const &a, char const (&b
template <isize N> gb_internal bool operator <= (String const &a, char const (&b)[N]) { return str_le(a, make_string(cast(u8 *)b, N-1)); }
template <isize N> gb_internal bool operator >= (String const &a, char const (&b)[N]) { return str_ge(a, make_string(cast(u8 *)b, N-1)); }
template <> bool operator == (String const &a, char const (&b)[1]) { return a.len == 0; }
template <> bool operator != (String const &a, char const (&b)[1]) { return a.len != 0; }
gb_internal gb_inline bool string_starts_with(String const &s, String const &prefix) {
if (prefix.len > s.len) {
return false;
@@ -234,11 +237,16 @@ gb_internal String string_split_iterator(String_Iterator *it, const char sep) {
return substring(it->str, start, end);
}
gb_internal gb_inline bool is_separator(u8 const &ch) {
return (ch == '/' || ch == '\\');
}
gb_internal gb_inline isize string_extension_position(String const &str) {
isize dot_pos = -1;
isize i = str.len;
while (i --> 0) {
if (str[i] == '\\' || str[i] == '/')
if (is_separator(str[i]))
break;
if (str[i] == '.') {
dot_pos = i;
@@ -273,6 +281,43 @@ gb_internal String string_trim_whitespace(String str) {
return str;
}
gb_internal String string_trim_trailing_whitespace(String str) {
while (str.len > 0) {
u8 c = str[str.len-1];
if (rune_is_whitespace(c) || c == 0) {
str.len -= 1;
} else {
break;
}
}
return str;
}
gb_internal String split_lines_first_line_from_array(Array<u8> const &array, gbAllocator allocator) {
String_Iterator it = {{array.data, array.count}, 0};
String line = string_split_iterator(&it, '\n');
line = string_trim_trailing_whitespace(line);
return line;
}
gb_internal Array<String> split_lines_from_array(Array<u8> const &array, gbAllocator allocator) {
Array<String> lines = {};
lines.allocator = allocator;
String_Iterator it = {{array.data, array.count}, 0};
for (;;) {
String line = string_split_iterator(&it, '\n');
if (line.len == 0) {
break;
}
line = string_trim_trailing_whitespace(line);
array_add(&lines, line);
}
return lines;
}
gb_internal bool string_contains_char(String const &s, u8 c) {
isize i;
@@ -292,8 +337,7 @@ gb_internal String filename_from_path(String s) {
if (i > 0) {
isize j = 0;
for (j = s.len-1; j >= 0; j--) {
if (s[j] == '/' ||
s[j] == '\\') {
if (is_separator(s[j])) {
break;
}
}
@@ -306,8 +350,7 @@ gb_internal String filename_from_path(String s) {
gb_internal String filename_without_directory(String s) {
isize j = 0;
for (j = s.len-1; j >= 0; j--) {
if (s[j] == '/' ||
s[j] == '\\') {
if (is_separator(s[j])) {
break;
}
}
@@ -370,7 +413,26 @@ gb_internal String copy_string(gbAllocator a, String const &s) {
return make_string(data, s.len);
}
gb_internal String normalize_path(gbAllocator a, String const &path, String const &sep) {
String s;
if (sep.len < 1) {
return path;
}
if (path.len < 1) {
s = STR_LIT("");
} else if (is_separator(path[path.len-1])) {
s = copy_string(a, path);
} else {
s = concatenate_strings(a, path, sep);
}
isize i;
for (i = 0; i < s.len; i++) {
if (is_separator(s.text[i])) {
s.text[i] = sep.text[0];
}
}
return s;
}
#if defined(GB_SYSTEM_WINDOWS)
-1
View File
@@ -769,7 +769,6 @@ gb_internal gbString type_to_string (Type *type, bool shorthand=true);
gb_internal gbString type_to_string (Type *type, gbAllocator allocator, bool shorthand=true);
gb_internal i64 type_size_of_internal(Type *t, TypePath *path);
gb_internal i64 type_align_of_internal(Type *t, TypePath *path);
gb_internal void init_map_internal_types(Type *type);
gb_internal Type * bit_set_to_int(Type *t);
gb_internal bool are_types_identical(Type *x, Type *y);
+3
View File
@@ -56,6 +56,9 @@ encoding_test:
$(ODIN) run encoding/json $(COMMON) -out:test_json
$(ODIN) run encoding/varint $(COMMON) -out:test_varint
$(ODIN) run encoding/xml $(COMMON) -out:test_xml
$(ODIN) run encoding/cbor $(COMMON) -out:test_cbor
$(ODIN) run encoding/hex $(COMMON) -out:test_hex
$(ODIN) run encoding/base64 $(COMMON) -out:test_base64
math_test:
$(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math
+3
View File
@@ -40,6 +40,9 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe |
%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b
%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b
%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b
%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b
%PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b
%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b
echo ---
echo Running core:math/noise tests
+61
View File
@@ -0,0 +1,61 @@
package test_encoding_base64
import "base:intrinsics"
import "core:encoding/base64"
import "core:fmt"
import "core:os"
import "core:reflect"
import "core:testing"
TEST_count := 0
TEST_fail := 0
when ODIN_TEST {
expect_value :: testing.expect_value
} else {
expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
TEST_count += 1
ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
if !ok {
TEST_fail += 1
fmt.printf("[%v] expected %v, got %v\n", loc, expected, value)
}
return ok
}
}
main :: proc() {
t := testing.T{}
test_encoding(&t)
test_decoding(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
os.exit(1)
}
}
@(test)
test_encoding :: proc(t: ^testing.T) {
expect_value(t, base64.encode(transmute([]byte)string("")), "")
expect_value(t, base64.encode(transmute([]byte)string("f")), "Zg==")
expect_value(t, base64.encode(transmute([]byte)string("fo")), "Zm8=")
expect_value(t, base64.encode(transmute([]byte)string("foo")), "Zm9v")
expect_value(t, base64.encode(transmute([]byte)string("foob")), "Zm9vYg==")
expect_value(t, base64.encode(transmute([]byte)string("fooba")), "Zm9vYmE=")
expect_value(t, base64.encode(transmute([]byte)string("foobar")), "Zm9vYmFy")
}
@(test)
test_decoding :: proc(t: ^testing.T) {
expect_value(t, string(base64.decode("")), "")
expect_value(t, string(base64.decode("Zg==")), "f")
expect_value(t, string(base64.decode("Zm8=")), "fo")
expect_value(t, string(base64.decode("Zm9v")), "foo")
expect_value(t, string(base64.decode("Zm9vYg==")), "foob")
expect_value(t, string(base64.decode("Zm9vYmE=")), "fooba")
expect_value(t, string(base64.decode("Zm9vYmFy")), "foobar")
}
@@ -0,0 +1,905 @@
package test_encoding_cbor
import "base:intrinsics"
import "core:bytes"
import "core:encoding/cbor"
import "core:fmt"
import "core:io"
import "core:math/big"
import "core:mem"
import "core:os"
import "core:reflect"
import "core:testing"
import "core:time"
TEST_count := 0
TEST_fail := 0
when ODIN_TEST {
expect :: testing.expect
expect_value :: testing.expect_value
errorf :: testing.errorf
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
TEST_count += 1
ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
if !ok {
TEST_fail += 1
fmt.printf("[%v] expected %v, got %v\n", loc, expected, value)
}
return ok
}
errorf :: proc(t: ^testing.T, fmts: string, args: ..any, loc := #caller_location) {
TEST_fail += 1
fmt.printf("[%v] ERROR: ", loc)
fmt.printf(fmts, ..args)
fmt.println()
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc)
fmt.printf("log: %v\n", v)
}
}
main :: proc() {
t := testing.T{}
test_marshalling(&t)
test_marshalling_maybe(&t)
test_marshalling_nil_maybe(&t)
test_marshalling_union(&t)
test_lying_length_array(&t)
test_decode_unsigned(&t)
test_encode_unsigned(&t)
test_decode_negative(&t)
test_encode_negative(&t)
test_decode_simples(&t)
test_encode_simples(&t)
test_decode_floats(&t)
test_encode_floats(&t)
test_decode_bytes(&t)
test_encode_bytes(&t)
test_decode_strings(&t)
test_encode_strings(&t)
test_decode_lists(&t)
test_encode_lists(&t)
test_decode_maps(&t)
test_encode_maps(&t)
test_decode_tags(&t)
test_encode_tags(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
os.exit(1)
}
}
Foo :: struct {
str: string,
cstr: cstring,
value: cbor.Value,
neg: cbor.Negative_U16,
pos: u16,
iamint: int,
base64: string `cbor_tag:"base64"`,
renamed: f32 `cbor:"renamed :)"`,
now: time.Time `cbor_tag:"1"`,
nowie: time.Time,
child: struct{
dyn: [dynamic]string,
mappy: map[string]int,
my_integers: [10]int,
},
my_bytes: []byte,
ennie: FooBar,
ennieb: FooBars,
quat: quaternion64,
comp: complex128,
important: rune,
no: cbor.Nil,
nos: cbor.Undefined,
yes: b32,
biggie: u64,
smallie: cbor.Negative_U64,
onetwenty: i128,
small_onetwenty: i128,
biggest: big.Int,
smallest: big.Int,
ignore_this: ^Foo `cbor:"-"`,
}
FooBar :: enum {
EFoo,
EBar,
}
FooBars :: bit_set[FooBar; u16]
@(test)
test_marshalling :: proc(t: ^testing.T) {
tracker: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracker, context.allocator)
context.allocator = mem.tracking_allocator(&tracker)
context.temp_allocator = context.allocator
defer mem.tracking_allocator_destroy(&tracker)
ev :: expect_value
{
nice := "16 is a nice number"
now := time.Time{_nsec = 1701117968 * 1e9}
f: Foo = {
str = "Hellope",
cstr = "Hellnope",
value = &cbor.Map{{u8(16), &nice}, {u8(32), u8(69)}},
neg = 68,
pos = 1212,
iamint = -256,
base64 = nice,
renamed = 123123.125,
now = now,
nowie = now,
child = {
dyn = [dynamic]string{"one", "two", "three", "four"},
mappy = map[string]int{"one" = 1, "two" = 2, "three" = 3, "four" = 4},
my_integers = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
},
my_bytes = []byte{},
ennie = .EFoo,
ennieb = {.EBar},
quat = quaternion(w=16, x=17, y=18, z=19),
comp = complex(32, 33),
important = '!',
no = cbor.Nil(uintptr(3)),
yes = true,
biggie = max(u64),
smallie = cbor.Negative_U64(max(u64)),
onetwenty = i128(12345),
small_onetwenty = -i128(max(u64)),
ignore_this = &Foo{},
}
big.atoi(&f.biggest, "1234567891011121314151617181920")
big.atoi(&f.smallest, "-1234567891011121314151617181920")
defer {
delete(f.child.dyn)
delete(f.child.mappy)
big.destroy(&f.biggest)
big.destroy(&f.smallest)
}
data, err := cbor.marshal(f, cbor.ENCODE_FULLY_DETERMINISTIC)
ev(t, err, nil)
defer delete(data)
decoded, derr := cbor.decode(string(data))
ev(t, derr, nil)
defer cbor.destroy(decoded)
diagnosis, eerr := cbor.to_diagnostic_format(decoded)
ev(t, eerr, nil)
defer delete(diagnosis)
ev(t, diagnosis, `{
"base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="),
"biggest": 2(h'f951a9fd3c158afdff08ab8e0'),
"biggie": 18446744073709551615,
"child": {
"dyn": [
"one",
"two",
"three",
"four"
],
"mappy": {
"one": 1,
"two": 2,
"four": 4,
"three": 3
},
"my_integers": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
]
},
"comp": [
32.0000,
33.0000
],
"cstr": "Hellnope",
"ennie": 0,
"ennieb": 512,
"iamint": -256,
"important": "!",
"my_bytes": h'',
"neg": -69,
"no": nil,
"nos": undefined,
"now": 1(1701117968),
"nowie": {
"_nsec": 1701117968000000000
},
"onetwenty": 12345,
"pos": 1212,
"quat": [
17.0000,
18.0000,
19.0000,
16.0000
],
"renamed :)": 123123.12500000,
"small_onetwenty": -18446744073709551615,
"smallest": 3(h'f951a9fd3c158afdff08ab8e0'),
"smallie": -18446744073709551616,
"str": "Hellope",
"value": {
16: "16 is a nice number",
32: 69
},
"yes": true
}`)
backf: Foo
uerr := cbor.unmarshal(string(data), &backf)
ev(t, uerr, nil)
defer {
delete(backf.str)
delete(backf.cstr)
cbor.destroy(backf.value)
delete(backf.base64)
for e in backf.child.dyn { delete(e) }
delete(backf.child.dyn)
for k in backf.child.mappy { delete(k) }
delete(backf.child.mappy)
delete(backf.my_bytes)
big.destroy(&backf.biggest)
big.destroy(&backf.smallest)
}
ev(t, backf.str, f.str)
ev(t, backf.cstr, f.cstr)
#partial switch v in backf.value {
case ^cbor.Map:
for entry, i in v {
fm := f.value.(^cbor.Map)
ev(t, entry.key, fm[i].key)
if str, is_str := entry.value.(^cbor.Text); is_str {
ev(t, str^, fm[i].value.(^cbor.Text)^)
} else {
ev(t, entry.value, fm[i].value)
}
}
case: errorf(t, "wrong type %v", v)
}
ev(t, backf.neg, f.neg)
ev(t, backf.iamint, f.iamint)
ev(t, backf.base64, f.base64)
ev(t, backf.renamed, f.renamed)
ev(t, backf.now, f.now)
ev(t, backf.nowie, f.nowie)
for e, i in f.child.dyn { ev(t, backf.child.dyn[i], e) }
for key, value in f.child.mappy { ev(t, backf.child.mappy[key], value) }
ev(t, backf.child.my_integers, f.child.my_integers)
ev(t, len(backf.my_bytes), 0)
ev(t, len(backf.my_bytes), len(f.my_bytes))
ev(t, backf.ennie, f.ennie)
ev(t, backf.ennieb, f.ennieb)
ev(t, backf.quat, f.quat)
ev(t, backf.comp, f.comp)
ev(t, backf.important, f.important)
ev(t, backf.no, nil)
ev(t, backf.nos, nil)
ev(t, backf.yes, f.yes)
ev(t, backf.biggie, f.biggie)
ev(t, backf.smallie, f.smallie)
ev(t, backf.onetwenty, f.onetwenty)
ev(t, backf.small_onetwenty, f.small_onetwenty)
ev(t, backf.ignore_this, nil)
s_equals, s_err := big.equals(&backf.smallest, &f.smallest)
ev(t, s_err, nil)
if !s_equals {
errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest))
}
b_equals, b_err := big.equals(&backf.biggest, &f.biggest)
ev(t, b_err, nil)
if !b_equals {
errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest))
}
}
for _, leak in tracker.allocation_map {
errorf(t, "%v leaked %m\n", leak.location, leak.size)
}
for bad_free in tracker.bad_free_array {
errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
}
}
@(test)
test_marshalling_maybe :: proc(t: ^testing.T) {
maybe_test: Maybe(int) = 1
data, err := cbor.marshal(maybe_test)
expect_value(t, err, nil)
val, derr := cbor.decode(string(data))
expect_value(t, derr, nil)
expect_value(t, cbor.to_diagnostic_format(val), "1")
maybe_dest: Maybe(int)
uerr := cbor.unmarshal(string(data), &maybe_dest)
expect_value(t, uerr, nil)
expect_value(t, maybe_dest, 1)
}
@(test)
test_marshalling_nil_maybe :: proc(t: ^testing.T) {
maybe_test: Maybe(int)
data, err := cbor.marshal(maybe_test)
expect_value(t, err, nil)
val, derr := cbor.decode(string(data))
expect_value(t, derr, nil)
expect_value(t, cbor.to_diagnostic_format(val), "nil")
maybe_dest: Maybe(int)
uerr := cbor.unmarshal(string(data), &maybe_dest)
expect_value(t, uerr, nil)
expect_value(t, maybe_dest, nil)
}
@(test)
test_marshalling_union :: proc(t: ^testing.T) {
My_Distinct :: distinct string
My_Enum :: enum {
One,
Two,
}
My_Struct :: struct {
my_enum: My_Enum,
}
My_Union :: union {
string,
My_Distinct,
My_Struct,
int,
}
{
test: My_Union = My_Distinct("Hello, World!")
data, err := cbor.marshal(test)
expect_value(t, err, nil)
val, derr := cbor.decode(string(data))
expect_value(t, derr, nil)
expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Distinct", "Hello, World!"])`)
dest: My_Union
uerr := cbor.unmarshal(string(data), &dest)
expect_value(t, uerr, nil)
expect_value(t, dest, My_Distinct("Hello, World!"))
}
My_Union_No_Nil :: union #no_nil {
string,
My_Distinct,
My_Struct,
int,
}
{
test: My_Union_No_Nil = My_Struct{.Two}
data, err := cbor.marshal(test)
expect_value(t, err, nil)
val, derr := cbor.decode(string(data))
expect_value(t, derr, nil)
expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Struct", {"my_enum": 1}])`)
dest: My_Union_No_Nil
uerr := cbor.unmarshal(string(data), &dest)
expect_value(t, uerr, nil)
expect_value(t, dest, My_Struct{.Two})
}
}
@(test)
test_lying_length_array :: proc(t: ^testing.T) {
// Input says this is an array of length max(u64), this should not allocate that amount.
input := []byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}
_, err := cbor.decode(string(input))
expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad.
}
@(test)
test_decode_unsigned :: proc(t: ^testing.T) {
expect_decoding(t, "\x00", "0", u8)
expect_decoding(t, "\x01", "1", u8)
expect_decoding(t, "\x0a", "10", u8)
expect_decoding(t, "\x17", "23", u8)
expect_decoding(t, "\x18\x18", "24", u8)
expect_decoding(t, "\x18\x19", "25", u8)
expect_decoding(t, "\x18\x64", "100", u8)
expect_decoding(t, "\x19\x03\xe8", "1000", u16)
expect_decoding(t, "\x1a\x00\x0f\x42\x40", "1000000", u32) // Million.
expect_decoding(t, "\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00", "1000000000000", u64) // Trillion.
expect_decoding(t, "\x1b\xff\xff\xff\xff\xff\xff\xff\xff", "18446744073709551615", u64) // max(u64).
}
@(test)
test_encode_unsigned :: proc(t: ^testing.T) {
expect_encoding(t, u8(0), "\x00")
expect_encoding(t, u8(1), "\x01")
expect_encoding(t, u8(10), "\x0a")
expect_encoding(t, u8(23), "\x17")
expect_encoding(t, u8(24), "\x18\x18")
expect_encoding(t, u8(25), "\x18\x19")
expect_encoding(t, u8(100), "\x18\x64")
expect_encoding(t, u16(1000), "\x19\x03\xe8")
expect_encoding(t, u32(1000000), "\x1a\x00\x0f\x42\x40") // Million.
expect_encoding(t, u64(1000000000000), "\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00") // Trillion.
expect_encoding(t, u64(18446744073709551615), "\x1b\xff\xff\xff\xff\xff\xff\xff\xff") // max(u64).
}
@(test)
test_decode_negative :: proc(t: ^testing.T) {
expect_decoding(t, "\x20", "-1", cbor.Negative_U8)
expect_decoding(t, "\x29", "-10", cbor.Negative_U8)
expect_decoding(t, "\x38\x63", "-100", cbor.Negative_U8)
expect_decoding(t, "\x39\x03\xe7", "-1000", cbor.Negative_U16)
// Negative max(u64).
expect_decoding(t, "\x3b\xff\xff\xff\xff\xff\xff\xff\xff", "-18446744073709551616", cbor.Negative_U64)
}
@(test)
test_encode_negative :: proc(t: ^testing.T) {
expect_encoding(t, cbor.Negative_U8(0), "\x20")
expect_encoding(t, cbor.Negative_U8(9), "\x29")
expect_encoding(t, cbor.Negative_U8(99), "\x38\x63")
expect_encoding(t, cbor.Negative_U16(999), "\x39\x03\xe7")
// Negative max(u64).
expect_encoding(t, cbor.Negative_U64(18446744073709551615), "\x3b\xff\xff\xff\xff\xff\xff\xff\xff")
}
@(test)
test_decode_simples :: proc(t: ^testing.T) {
expect_decoding(t, "\xf4", "false", bool)
expect_decoding(t, "\xf5", "true", bool)
expect_decoding(t, "\xf6", "nil", cbor.Nil)
expect_decoding(t, "\xf7", "undefined", cbor.Undefined)
expect_decoding(t, "\xf0", "simple(16)", cbor.Simple)
expect_decoding(t, "\xf8\xff", "simple(255)", cbor.Atom)
}
@(test)
test_encode_simples :: proc(t: ^testing.T) {
expect_encoding(t, bool(false), "\xf4")
expect_encoding(t, bool(true), "\xf5")
expect_encoding(t, cbor.Nil{}, "\xf6") // default value for a distinct rawptr, in this case Nil.
expect_encoding(t, cbor.Undefined{}, "\xf7") // default value for a distinct rawptr, in this case Undefined.
expect_encoding(t, cbor.Simple(16), "\xf0") // simple(16)
expect_encoding(t, cbor.Simple(255), "\xf8\xff") // simple(255)
}
@(test)
test_decode_floats :: proc(t: ^testing.T) {
expect_float(t, "\xf9\x00\x00", f16(0.0))
expect_float(t, "\xf9\x80\x00", f16(-0.0))
expect_float(t, "\xf9\x3c\x00", f16(1.0))
expect_float(t, "\xfb\x3f\xf1\x99\x99\x99\x99\x99\x9a", f64(1.1))
expect_float(t, "\xf9\x3e\x00", f16(1.5))
expect_float(t, "\xf9\x7b\xff", f16(65504.0))
expect_float(t, "\xfa\x47\xc3\x50\x00", f32(100000.0))
expect_float(t, "\xfa\x7f\x7f\xff\xff", f32(3.4028234663852886e+38))
expect_float(t, "\xfb\x7e\x37\xe4\x3c\x88\x00\x75\x9c", f64(1.0e+300))
expect_float(t, "\xf9\x00\x01", f16(5.960464477539063e-8))
expect_float(t, "\xf9\x04\x00", f16(0.00006103515625))
expect_float(t, "\xf9\xc4\x00", f16(-4.0))
expect_float(t, "\xfb\xc0\x10\x66\x66\x66\x66\x66\x66", f64(-4.1))
expect_decoding(t, "\xf9\x7c\x00", "+Inf", f16)
expect_decoding(t, "\xf9\x7e\x00", "NaN", f16)
expect_decoding(t, "\xf9\xfc\x00", "-Inf", f16)
expect_decoding(t, "\xfa\x7f\x80\x00\x00", "+Inf", f32)
expect_decoding(t, "\xfa\x7f\xc0\x00\x00", "NaN", f32)
expect_decoding(t, "\xfa\xff\x80\x00\x00", "-Inf", f32)
expect_decoding(t, "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00", "+Inf", f64)
expect_decoding(t, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00", "NaN", f64)
expect_decoding(t, "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00", "-Inf", f64)
}
@(test)
test_encode_floats :: proc(t: ^testing.T) {
expect_encoding(t, f16(0.0), "\xf9\x00\x00")
expect_encoding(t, f16(-0.0), "\xf9\x80\x00")
expect_encoding(t, f16(1.0), "\xf9\x3c\x00")
expect_encoding(t, f64(1.1), "\xfb\x3f\xf1\x99\x99\x99\x99\x99\x9a")
expect_encoding(t, f16(1.5), "\xf9\x3e\x00")
expect_encoding(t, f16(65504.0), "\xf9\x7b\xff")
expect_encoding(t, f32(100000.0), "\xfa\x47\xc3\x50\x00")
expect_encoding(t, f32(3.4028234663852886e+38), "\xfa\x7f\x7f\xff\xff")
expect_encoding(t, f64(1.0e+300), "\xfb\x7e\x37\xe4\x3c\x88\x00\x75\x9c")
expect_encoding(t, f16(5.960464477539063e-8), "\xf9\x00\x01")
expect_encoding(t, f16(0.00006103515625), "\xf9\x04\x00")
expect_encoding(t, f16(-4.0), "\xf9\xc4\x00")
expect_encoding(t, f64(-4.1), "\xfb\xc0\x10\x66\x66\x66\x66\x66\x66")
}
@(test)
test_decode_bytes :: proc(t: ^testing.T) {
expect_decoding(t, "\x40", "h''", ^cbor.Bytes)
expect_decoding(t, "\x44\x01\x02\x03\x04", "h'1234'", ^cbor.Bytes)
// Indefinite lengths
expect_decoding(t, "\x5f\x42\x01\x02\x43\x03\x04\x05\xff", "h'12345'", ^cbor.Bytes)
}
@(test)
test_encode_bytes :: proc(t: ^testing.T) {
expect_encoding(t, &cbor.Bytes{}, "\x40")
expect_encoding(t, &cbor.Bytes{1, 2, 3, 4}, "\x44\x01\x02\x03\x04")
// Indefinite lengths
expect_streamed_encoding(t, "\x5f\x42\x01\x02\x43\x03\x04\x05\xff", &cbor.Bytes{1, 2}, &cbor.Bytes{3, 4, 5})
}
@(test)
test_decode_strings :: proc(t: ^testing.T) {
expect_decoding(t, "\x60", `""`, ^cbor.Text)
expect_decoding(t, "\x61\x61", `"a"`, ^cbor.Text)
expect_decoding(t, "\x64\x49\x45\x54\x46", `"IETF"`, ^cbor.Text)
expect_decoding(t, "\x62\x22\x5c", `""\"`, ^cbor.Text)
expect_decoding(t, "\x62\xc3\xbc", `"ü"`, ^cbor.Text)
expect_decoding(t, "\x63\xe6\xb0\xb4", `"水"`, ^cbor.Text)
expect_decoding(t, "\x64\xf0\x90\x85\x91", `"𐅑"`, ^cbor.Text)
// Indefinite lengths
expect_decoding(t, "\x7f\x65\x73\x74\x72\x65\x61\x64\x6d\x69\x6e\x67\xff", `"streaming"`, ^cbor.Text)
}
@(test)
test_encode_strings :: proc(t: ^testing.T) {
expect_encoding(t, &cbor.Text{}, "\x60")
a := "a"
expect_encoding(t, &a, "\x61\x61")
b := "IETF"
expect_encoding(t, &b, "\x64\x49\x45\x54\x46")
c := "\"\\"
expect_encoding(t, &c, "\x62\x22\x5c")
d := "ü"
expect_encoding(t, &d, "\x62\xc3\xbc")
e := "水"
expect_encoding(t, &e, "\x63\xe6\xb0\xb4")
f := "𐅑"
expect_encoding(t, &f, "\x64\xf0\x90\x85\x91")
// Indefinite lengths
sa := "strea"
sb := "ming"
expect_streamed_encoding(t, "\x7f\x65\x73\x74\x72\x65\x61\x64\x6d\x69\x6e\x67\xff", &sa, &sb)
}
@(test)
test_decode_lists :: proc(t: ^testing.T) {
expect_decoding(t, "\x80", "[]", ^cbor.Array)
expect_decoding(t, "\x83\x01\x02\x03", "[1, 2, 3]", ^cbor.Array)
expect_decoding(t, "\x83\x01\x82\x02\x03\x82\x04\x05", "[1, [2, 3], [4, 5]]", ^cbor.Array)
expect_decoding(t, "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19", "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", ^cbor.Array)
expect_decoding(t, "\x82\x61\x61\xa1\x61\x62\x61\x63", `["a", {"b": "c"}]`, ^cbor.Array)
// Indefinite lengths
expect_decoding(t, "\x9f\xff", "[]", ^cbor.Array)
expect_decoding(t, "\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff", "[1, [2, 3], [4, 5]]", ^cbor.Array)
expect_decoding(t, "\x9f\x01\x82\x02\x03\x82\x04\x05\xff", "[1, [2, 3], [4, 5]]", ^cbor.Array)
expect_decoding(t, "\x83\x01\x82\x02\x03\x9f\x04\x05\xff", "[1, [2, 3], [4, 5]]", ^cbor.Array)
expect_decoding(t, "\x83\x01\x9f\x02\x03\xff\x82\x04\x05", "[1, [2, 3], [4, 5]]", ^cbor.Array)
expect_decoding(t, "\x9f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19\xff", "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", ^cbor.Array)
expect_decoding(t, "\x82\x61\x61\xbf\x61\x62\x61\x63\xff", `["a", {"b": "c"}]`, ^cbor.Array)
}
@(test)
test_encode_lists :: proc(t: ^testing.T) {
expect_encoding(t, &cbor.Array{}, "\x80")
expect_encoding(t, &cbor.Array{u8(1), u8(2), u8(3)}, "\x83\x01\x02\x03")
expect_encoding(t, &cbor.Array{u8(1), &cbor.Array{u8(2), u8(3)}, &cbor.Array{u8(4), u8(5)}}, "\x83\x01\x82\x02\x03\x82\x04\x05")
expect_encoding(t, &cbor.Array{u8(1), u8(2), u8(3), u8(4), u8(5), u8(6), u8(7), u8(8), u8(9), u8(10), u8(11), u8(12), u8(13), u8(14), u8(15), u8(16), u8(17), u8(18), u8(19), u8(20), u8(21), u8(22), u8(23), u8(24), u8(25)}, "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19")
{
a := "a"
b := "b"
c := "c"
expect_encoding(t, &cbor.Array{&a, &cbor.Map{{&b, &c}}}, "\x82\x61\x61\xa1\x61\x62\x61\x63")
}
// Indefinite lengths
expect_streamed_encoding(t, "\x9f\xff", &cbor.Array{})
{
bytes.buffer_reset(&buf)
err: cbor.Encode_Error
err = cbor.encode_stream_begin(stream, .Array)
expect_value(t, err, nil)
{
err = cbor.encode_stream_array_item(encoder, u8(1))
expect_value(t, err, nil)
err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)})
expect_value(t, err, nil)
err = cbor.encode_stream_begin(stream, .Array)
expect_value(t, err, nil)
{
err = cbor.encode_stream_array_item(encoder, u8(4))
expect_value(t, err, nil)
err = cbor.encode_stream_array_item(encoder, u8(5))
expect_value(t, err, nil)
}
err = cbor.encode_stream_end(stream)
expect_value(t, err, nil)
}
err = cbor.encode_stream_end(stream)
expect_value(t, err, nil)
expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff")))
}
{
bytes.buffer_reset(&buf)
err: cbor.Encode_Error
err = cbor._encode_u8(stream, 2, .Array)
expect_value(t, err, nil)
a := "a"
err = cbor.encode(encoder, &a)
expect_value(t, err, nil)
{
err = cbor.encode_stream_begin(stream, .Map)
expect_value(t, err, nil)
b := "b"
c := "c"
err = cbor.encode_stream_map_entry(encoder, &b, &c)
expect_value(t, err, nil)
err = cbor.encode_stream_end(stream)
expect_value(t, err, nil)
}
expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff")))
}
}
@(test)
test_decode_maps :: proc(t: ^testing.T) {
expect_decoding(t, "\xa0", "{}", ^cbor.Map)
expect_decoding(t, "\xa2\x01\x02\x03\x04", "{1: 2, 3: 4}", ^cbor.Map)
expect_decoding(t, "\xa2\x61\x61\x01\x61\x62\x82\x02\x03", `{"a": 1, "b": [2, 3]}`, ^cbor.Map)
expect_decoding(t, "\xa5\x61\x61\x61\x41\x61\x62\x61\x42\x61\x63\x61\x43\x61\x64\x61\x44\x61\x65\x61\x45", `{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E"}`, ^cbor.Map)
// Indefinite lengths
expect_decoding(t, "\xbf\x61\x61\x01\x61\x62\x9f\x02\x03\xff\xff", `{"a": 1, "b": [2, 3]}`, ^cbor.Map)
expect_decoding(t, "\xbf\x63\x46\x75\x6e\xf5\x63\x41\x6d\x74\x21\xff", `{"Fun": true, "Amt": -2}`, ^cbor.Map)
}
@(test)
test_encode_maps :: proc(t: ^testing.T) {
expect_encoding(t, &cbor.Map{}, "\xa0")
expect_encoding(t, &cbor.Map{{u8(1), u8(2)}, {u8(3), u8(4)}}, "\xa2\x01\x02\x03\x04")
a := "a"
b := "b"
// NOTE: also tests the deterministic nature because it has to swap/sort the entries.
expect_encoding(t, &cbor.Map{{&b, &cbor.Array{u8(2), u8(3)}}, {&a, u8(1)}}, "\xa2\x61\x61\x01\x61\x62\x82\x02\x03")
fun := "Fun"
amt := "Amt"
expect_streamed_encoding(t, "\xbf\x63\x46\x75\x6e\xf5\x63\x41\x6d\x74\x21\xff", &cbor.Map{{&fun, true}, {&amt, cbor.Negative_U8(1)}})
}
@(test)
test_decode_tags :: proc(t: ^testing.T) {
// Tag number 2 (unsigned bignumber), value bytes, max(u64) + 1.
expect_tag(t, "\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_UNSIGNED_BIG_NR, "2(h'100000000')")
// Tag number 3 (negative bignumber), value bytes, negative max(u64) - 1.
expect_tag(t, "\xc3\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_NEGATIVE_BIG_NR, "3(h'100000000')")
expect_tag(t, "\xc1\x1a\x51\x4b\x67\xb0", cbor.TAG_EPOCH_TIME_NR, "1(1363896240)")
expect_tag(t, "\xc1\xfb\x41\xd4\x52\xd9\xec\x20\x00\x00", cbor.TAG_EPOCH_TIME_NR, "1(1363896240.5000000000000000)")
expect_tag(t, "\xd8\x18\x45\x64\x49\x45\x54\x46", cbor.TAG_CBOR_NR, "24(h'6449455446')")
}
@(test)
test_encode_tags :: proc(t: ^testing.T) {
expect_encoding(t, &cbor.Tag{cbor.TAG_UNSIGNED_BIG_NR, &cbor.Bytes{1, 0, 0, 0, 0, 0, 0, 0, 0}}, "\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00")
expect_encoding(t, &cbor.Tag{cbor.TAG_EPOCH_TIME_NR, u32(1363896240)}, "\xc1\x1a\x51\x4b\x67\xb0")
expect_encoding(t, &cbor.Tag{cbor.TAG_EPOCH_TIME_NR, f64(1363896240.500)}, "\xc1\xfb\x41\xd4\x52\xd9\xec\x20\x00\x00")
}
// Helpers
expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: typeid, loc := #caller_location) {
res, err := cbor.decode(encoded)
defer cbor.destroy(res)
expect_value(t, reflect.union_variant_typeid(res), type, loc)
expect_value(t, err, nil, loc)
str := cbor.to_diagnostic_format(res, padding=-1)
defer delete(str)
expect_value(t, str, decoded, loc)
}
expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) {
res, err := cbor.decode(encoded)
defer cbor.destroy(res)
expect_value(t, err, nil, loc)
if tag, is_tag := res.(^cbor.Tag); is_tag {
expect_value(t, tag.number, nr, loc)
str := cbor.to_diagnostic_format(tag, padding=-1)
defer delete(str)
expect_value(t, str, value_decoded, loc)
} else {
errorf(t, "Value %#v is not a tag", res, loc)
}
}
expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #caller_location) where intrinsics.type_is_float(T) {
res, err := cbor.decode(encoded)
defer cbor.destroy(res)
expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc)
expect_value(t, err, nil, loc)
#partial switch r in res {
case f16:
when T == f16 { expect_value(t, res, expected, loc) } else { unreachable() }
case f32:
when T == f32 { expect_value(t, res, expected, loc) } else { unreachable() }
case f64:
when T == f64 { expect_value(t, res, expected, loc) } else { unreachable() }
case:
unreachable()
}
}
buf: bytes.Buffer
stream := bytes.buffer_to_stream(&buf)
encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}}
expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) {
bytes.buffer_reset(&buf)
err := cbor.encode(encoder, val)
expect_value(t, err, nil, loc)
expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc)
}
expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) {
bytes.buffer_reset(&buf)
for value, i in values {
err: cbor.Encode_Error
err2: cbor.Encode_Error
#partial switch v in value {
case ^cbor.Bytes:
if i == 0 { err = cbor.encode_stream_begin(stream, .Bytes) }
err2 = cbor._encode_bytes(encoder, v^)
case ^cbor.Text:
if i == 0 { err = cbor.encode_stream_begin(stream, .Text) }
err2 = cbor._encode_text(encoder, v^)
case ^cbor.Array:
if i == 0 { err = cbor.encode_stream_begin(stream, .Array) }
for item in v {
err2 = cbor.encode_stream_array_item(encoder, item)
if err2 != nil { break }
}
case ^cbor.Map:
err = cbor.encode_stream_begin(stream, .Map)
for item in v {
err2 = cbor.encode_stream_map_entry(encoder, item.key, item.value)
if err2 != nil { break }
}
case:
errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value))
}
expect_value(t, err, nil, loc)
expect_value(t, err2, nil, loc)
}
err := cbor.encode_stream_end(stream)
expect_value(t, err, nil, loc)
expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc)
}
@@ -4,7 +4,6 @@ import "core:encoding/hex"
import "core:testing"
import "core:fmt"
import "core:os"
import "core:bytes"
TEST_count := 0
TEST_fail := 0
+6 -6
View File
@@ -2283,7 +2283,7 @@ IHeap :: struct #raw_union {
}
IHeap_VTable :: struct {
using id3d12devicechild_vtable: IDeviceChild_VTable,
GetDesc: proc "system" (this: ^IHeap) -> HEAP_DESC,
GetDesc: proc "system" (this: ^IHeap, pRetValue: ^HEAP_DESC) -> ^HEAP_DESC,
}
@@ -2297,7 +2297,7 @@ IResource_VTable :: struct {
using id3d12devicechild_vtable: IDeviceChild_VTable,
Map: proc "system" (this: ^IResource, Subresource: u32, pReadRange: ^RANGE, ppData: ^rawptr) -> HRESULT,
Unmap: proc "system" (this: ^IResource, Subresource: u32, pWrittenRange: ^RANGE),
GetDesc: proc "system" (this: ^IResource) -> RESOURCE_DESC,
GetDesc: proc "system" (this: ^IResource, pRetValue: ^RESOURCE_DESC) -> ^RESOURCE_DESC,
GetGPUVirtualAddress: proc "system" (this: ^IResource) -> GPU_VIRTUAL_ADDRESS,
WriteToSubresource: proc "system" (this: ^IResource, DstSubresource: u32, pDstBox: ^BOX, pSrcData: rawptr, SrcRowPitch: u32, SrcDepthPitch: u32) -> HRESULT,
ReadFromSubresource: proc "system" (this: ^IResource, pDstData: rawptr, DstRowPitch: u32, DstDepthPitch: u32, SrcSubresource: u32, pSrcBox: ^BOX) -> HRESULT,
@@ -2514,7 +2514,7 @@ ICommandQueue_VTable :: struct {
Wait: proc "system" (this: ^ICommandQueue, pFence: ^IFence, Value: u64) -> HRESULT,
GetTimestampFrequency: proc "system" (this: ^ICommandQueue, pFrequency: ^u64) -> HRESULT,
GetClockCalibration: proc "system" (this: ^ICommandQueue, pGpuTimestamp: ^u64, pCpuTimestamp: ^u64) -> HRESULT,
GetDesc: proc "system" (this: ^ICommandQueue) -> COMMAND_QUEUE_DESC,
GetDesc: proc "system" (this: ^ICommandQueue, pRetVal: ^COMMAND_QUEUE_DESC) -> ^COMMAND_QUEUE_DESC,
}
@@ -2712,7 +2712,7 @@ IProtectedResourceSession :: struct #raw_union {
}
IProtectedResourceSession_VTable :: struct {
using id3d12protectedsession_vtable: IProtectedSession_VTable,
GetDesc: proc "system" (this: ^IProtectedResourceSession) -> PROTECTED_RESOURCE_SESSION_DESC,
GetDesc: proc "system" (this: ^IProtectedResourceSession, pRetVal: ^PROTECTED_RESOURCE_SESSION_DESC) -> ^PROTECTED_RESOURCE_SESSION_DESC,
}
@@ -3492,7 +3492,7 @@ IProtectedResourceSession1 :: struct #raw_union {
}
IProtectedResourceSession1_VTable :: struct {
using id3d12protectedresourcesession_vtable: IProtectedResourceSession_VTable,
GetDesc1: proc "system" (this: ^IProtectedResourceSession1) -> PROTECTED_RESOURCE_SESSION_DESC1,
GetDesc1: proc "system" (this: ^IProtectedResourceSession1, pRetVal: ^PROTECTED_RESOURCE_SESSION_DESC1) -> ^PROTECTED_RESOURCE_SESSION_DESC1,
}
@@ -3545,7 +3545,7 @@ IResource2 :: struct #raw_union {
}
IResource2_VTable :: struct {
using id3d12resource1_vtable: IResource1_VTable,
GetDesc1: proc "system" (this: ^IResource2) -> RESOURCE_DESC1,
GetDesc1: proc "system" (this: ^IResource2, pRetVal: ^RESOURCE_DESC1) -> ^RESOURCE_DESC1,
}