2025-04-02 14:05:22 +08:00
module binary
struct EncodeState {
mut :
b [ ] u8
// pre-allocated buffers
b2 [ ] u8 = [ u8 ( 0 ) , 0 ]
b4 [ ] u8 = [ u8 ( 0 ) , 0 , 0 , 0 ]
b8 [ ] u8 = [ u8 ( 0 ) , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]
big_endian bool
}
@ [ params ]
pub struct EncodeConfig {
pub mut :
buffer_len int = 1024
big_endian bool // use big endian encoding the data
}
// encode_binary encode a T type data into u8 array.
// for encoding struct, you can use `@[serialize: '-']` to skip field.
2025-10-29 17:58:05 +08:00
// Note: `shared` fields in struct will be skipped.
2025-04-02 14:05:22 +08:00
pub fn encode_binary [ T ] ( obj T , config EncodeConfig ) ! [ ] u8 {
mut s := EncodeState {
b : [ ] u8 { cap : config . buffer_len }
big_endian : config . big_endian
}
$ if T is $ array {
encode_array ( mut s , obj ) !
} $ else $ if T is $ string {
encode_string ( mut s , obj ) !
} $ else $ if T is $ struct {
encode_struct ( mut s , obj ) !
} $ else $ if T is $ map {
encode_map ( mut s , obj ) !
} $ else {
encode_primitive ( mut s , obj ) !
}
return s . b
}
2026-02-26 20:57:29 +03:00
@ [ inline ]
fn normalize_attr_arg ( value string ) string {
mut normalized := value . trim_space ( )
if normalized . len > 1 {
if ( normalized [ 0 ] == ` ' ` && normalized [ normalized . len - 1 ] == ` ' ` )
|| ( normalized [ 0 ] == ` " ` && normalized [ normalized . len - 1 ] == ` " ` ) {
normalized = normalized [ 1 .. normalized . len - 1 ]
}
}
return normalized
}
2025-04-02 14:05:22 +08:00
fn encode_struct [ T ] ( mut s EncodeState , obj T ) ! {
$ for field in T . fields {
mut is_skip := false
for attr in field . attrs {
f := attr . split_any ( ' : ' )
if f . len == 2 {
match f [ 0 ] . trim_space ( ) {
' s e r i a l i z e ' {
// @[serialize:'-']
2026-02-26 20:57:29 +03:00
if normalize_attr_arg ( f [ 1 ] ) == ' - ' {
2025-04-02 14:05:22 +08:00
is_skip = true
}
}
else { }
}
}
}
if ! is_skip {
2025-10-29 17:58:05 +08:00
// TODO: support shared field in struct, currently skip.
$ if field . typ ! is $ shared {
value := obj . $ ( field . name )
$ if field . typ is $ array {
encode_array ( mut s , value ) !
} $ else $ if field . typ is $ string {
encode_string ( mut s , value ) !
} $ else $ if field . typ is $ struct {
encode_struct ( mut s , value ) !
} $ else $ if field . typ is $ map {
encode_map ( mut s , value ) !
} $ else {
encode_primitive ( mut s , value ) !
}
2025-04-02 14:05:22 +08:00
}
}
}
}
// help unions for bypass `-cstrict`/ `-Wstrict-aliasing` check.
union U32_F32 {
u u32
f f32
}
union U64_F64 {
u u64
f f64
}
fn encode_primitive [ T ] ( mut s EncodeState , value T ) ! {
$ if T is int {
// NOTE: `int` always use 64bit
s . put_u64 ( u64 ( value ) )
} $ else $ if T is u8 {
s . put_u8 ( u8 ( value ) )
} $ else $ if T is u16 {
s . put_u16 ( u16 ( value ) )
} $ else $ if T is u32 {
s . put_u32 ( u32 ( value ) )
} $ else $ if T is u64 {
s . put_u64 ( u64 ( value ) )
} $ else $ if T is i8 {
s . put_u8 ( u8 ( value ) )
} $ else $ if T is i16 {
s . put_u16 ( u16 ( value ) )
} $ else $ if T is i32 {
s . put_u32 ( u32 ( value ) )
} $ else $ if T is i64 {
s . put_u64 ( u64 ( value ) )
} $ else $ if T is f32 {
unsafe { s . put_u32 ( U32_F32 { f : value } . u ) }
} $ else $ if T is f64 {
unsafe { s . put_u64 ( U64_F64 { f : value } . u ) }
} $ else $ if T is bool {
s . put_u8 ( u8 ( value ) )
} $ else $ if T is rune {
s . put_u32 ( u32 ( value ) )
} $ else $ if T is isize {
if sizeof ( isize ) == 4 {
s . put_u32 ( u32 ( value ) )
} else {
s . put_u64 ( u64 ( value ) )
}
} $ else $ if T is usize {
if sizeof ( usize ) == 4 {
s . put_u32 ( u32 ( value ) )
} else {
s . put_u64 ( u64 ( value ) )
}
} $ else $ if T is voidptr {
s . put_u64 ( u64 ( value ) )
} $ else {
// TODO: `any` type support?
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : u n s u p p o r t e d t y p e $ { typeof ( value ) . name } ' )
2025-04-02 14:05:22 +08:00
}
}
fn encode_array [ T ] ( mut s EncodeState , arr [ ] T ) ! {
s . put_u64 ( u64 ( arr . len ) )
$ if T is u8 {
// optimization for `[]u8`
s . b << arr
} $ else {
for element in arr {
$ if T is $ string {
encode_string ( mut s , element ) !
} $ else $ if T is $ struct {
encode_struct ( mut s , element ) !
} $ else $ if T is $ map {
encode_map ( mut s , element ) !
} $ else {
encode_primitive ( mut s , element ) !
}
}
}
}
fn encode_string ( mut s EncodeState , str string ) ! {
s . put_u64 ( u64 ( str . len ) )
s . b << str . bytes ( )
}
fn encode_map [ K , V ] ( mut s EncodeState , m map [ K ] V ) ! {
s . put_u64 ( u64 ( m . len ) )
for k , v in m {
// encode key first
// Maps can have keys of type string, rune, integer, float or voidptr.
$ if K is $ string {
encode_string ( mut s , k ) !
} $ else {
encode_primitive ( mut s , k ) !
}
// encode value
$ if V is $ string {
encode_string ( mut s , v ) !
} $ else $ if V is $ struct {
encode_struct ( mut s , v ) !
} $ else $ if V is $ map {
encode_map ( mut s , v ) !
} $ else {
encode_primitive ( mut s , v ) !
}
}
}
struct DecodeState {
mut :
b [ ] u8
b2 [ ] u8 = [ u8 ( 0 ) , 0 ]
b4 [ ] u8 = [ u8 ( 0 ) , 0 , 0 , 0 ]
b8 [ ] u8 = [ u8 ( 0 ) , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]
offset int
big_endian bool
}
@ [ params ]
pub struct DecodeConfig {
pub mut :
buffer_len int = 1024
big_endian bool // use big endian decode the data
}
// decode_binary decode a u8 array into T type data.
// for decoding struct, you can use `@[serialize: '-']` to skip field.
2025-10-29 17:58:05 +08:00
// Note: `shared` fields in struct will be skipped.
2025-04-02 14:05:22 +08:00
pub fn decode_binary [ T ] ( b [ ] u8 , config DecodeConfig ) ! T {
mut s := DecodeState {
b : b
big_endian : config . big_endian
}
$ if T is $ array {
return decode_array ( mut s , T { } ) !
} $ else $ if T is $ string {
return decode_string ( mut s ) !
} $ else $ if T is $ struct {
return decode_struct ( mut s , T { } ) !
} $ else $ if T is $ map {
return decode_map ( mut s , T { } ) !
} $ else {
return decode_primitive ( mut s , unsafe { T ( 0 ) } ) !
}
}
fn decode_struct [ T ] ( mut s DecodeState , _ T ) ! T {
mut obj := T { }
$ for field in T . fields {
mut is_skip := false
for attr in field . attrs {
f := attr . split_any ( ' : ' )
if f . len == 2 {
match f [ 0 ] . trim_space ( ) {
' s e r i a l i z e ' {
// @[serialize:'-']
2026-02-26 20:57:29 +03:00
if normalize_attr_arg ( f [ 1 ] ) == ' - ' {
2025-04-02 14:05:22 +08:00
is_skip = true
}
}
else { }
}
}
}
if ! is_skip {
2025-10-29 17:58:05 +08:00
// TODO: support shared field in struct, currently skip.
$ if field . typ ! is $ shared {
$ if field . typ is $ array {
obj . $ ( field . name ) = decode_array ( mut s , obj . $ ( field . name ) ) !
} $ else $ if field . typ is $ string {
obj . $ ( field . name ) = decode_string ( mut s ) !
} $ else $ if field . typ is $ struct {
obj . $ ( field . name ) = decode_struct ( mut s , obj . $ ( field . name ) ) !
} $ else $ if field . typ is $ map {
obj . $ ( field . name ) = decode_map ( mut s , obj . $ ( field . name ) ) !
} $ else {
obj . $ ( field . name ) = decode_primitive ( mut s , obj . $ ( field . name ) ) !
}
2025-04-02 14:05:22 +08:00
}
}
}
return obj
}
fn decode_primitive [ T ] ( mut s DecodeState , value T ) ! T {
$ if T is int {
// NOTE: int always use 64bit
return T ( s . get_u64 ( ) ! )
} $ else $ if T is u8 {
return T ( s . get_u8 ( ) ! )
} $ else $ if T is u16 {
return T ( s . get_u16 ( ) ! )
} $ else $ if T is u32 {
return T ( s . get_u32 ( ) ! )
} $ else $ if T is u64 {
return T ( s . get_u64 ( ) ! )
} $ else $ if T is i8 {
return T ( s . get_u8 ( ) ! )
} $ else $ if T is i16 {
return T ( s . get_u16 ( ) ! )
} $ else $ if T is i32 {
return T ( s . get_u32 ( ) ! )
} $ else $ if T is i64 {
return T ( s . get_u64 ( ) ! )
} $ else $ if T is f32 {
v := s . get_u32 ( ) !
return unsafe {
U32_F32 {
u : v
} . f
}
} $ else $ if T is f64 {
v := s . get_u64 ( ) !
return unsafe {
U64_F64 {
u : v
} . f
}
} $ else $ if T is bool {
return s . get_u8 ( ) ! != 0
} $ else $ if T is rune {
return T ( s . get_u32 ( ) ! )
} $ else $ if T is isize {
if sizeof ( isize ) == 4 {
return T ( s . get_u32 ( ) ! )
} else {
return T ( s . get_u64 ( ) ! )
}
} $ else $ if T is usize {
if sizeof ( usize ) == 4 {
return T ( s . get_u32 ( ) ! )
} else {
return T ( s . get_u64 ( ) ! )
}
} $ else $ if T is voidptr {
return T ( s . get_u64 ( ) ! )
} $ else {
// TODO: `any` type support?
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : u n s u p p o r t e d t y p e $ { typeof ( value ) . name } ' )
2025-04-02 14:05:22 +08:00
}
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : i m p o s s i b l e e r r o r ' )
2025-04-02 14:05:22 +08:00
}
fn decode_array [ T ] ( mut s DecodeState , _ [ ] T ) ! [ ] T {
len := int ( s . get_u64 ( ) ! )
if len <= 0 || s . offset + len > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : i n v a l i d a r r a y l e n g t h d e c o d e f r o m s t r e a m ' )
2025-04-02 14:05:22 +08:00
}
mut arr := [ ] T { cap : len }
$ if T is u8 {
// optimization for `[]u8`
arr << s . b [ s . offset .. s . offset + len ]
s . offset += len
} $ else {
for _ in 0 .. len {
if s . offset >= s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : u n e x p e c t e d e n d o f d a t a ' )
2025-04-02 14:05:22 +08:00
}
$ if T is $ array {
arr << decode_array ( mut s , T { } ) !
} $ else $ if T is $ string {
arr << decode_string ( mut s ) !
} $ else $ if T is $ struct {
arr << decode_struct ( mut s , T { } ) !
} $ else $ if T is $ map {
arr << decode_map ( mut s , T { } ) !
} $ else {
arr << decode_primitive ( mut s , unsafe { T ( 0 ) } ) !
}
}
}
return arr
}
fn decode_string ( mut s DecodeState ) ! string {
len := int ( s . get_u64 ( ) ! )
if len <= 0 || s . offset + len > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : i n v a l i d s t r i n g l e n g t h d e c o d e f r o m s t r e a m ' )
2025-04-02 14:05:22 +08:00
}
str := unsafe { s . b [ s . offset .. s . offset + len ] . bytestr ( ) }
s . offset += len
return str
}
// `Any` is a sum type that lists the possible types to be decoded and used.
type Any = int
| bool
| f64
| f32
| i64
| i32
| i16
| i8
| map [ string ] Any
| map [ int ] Any
| string
| u64
| u32
| u16
| u8
| rune
| isize
| usize
| [ ] Any
fn decode_map [ K , V ] ( mut s DecodeState , _ map [ K ] V ) ! map [ K ] V {
len := int ( s . get_u64 ( ) ! )
if len <= 0 || s . offset + len > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : i n v a l i d m a p l e n g t h d e c o d e f r o m s t r e a m ' )
2025-04-02 14:05:22 +08:00
}
mut m := map [ K ] V { }
for _ in 0 .. len {
// decode key first
// Maps can have keys of type string, rune, integer, float or voidptr.
mut k := Any ( 0 )
$ if K is $ string {
k = decode_string ( mut s ) !
} $ else {
k = decode_primitive ( mut s , unsafe { K ( 0 ) } ) !
}
// decode value
2025-04-13 16:32:13 +08:00
$ if V is $ struct {
v := decode_struct ( mut s , V { } ) !
m [ k as K ] = v
2025-04-02 14:05:22 +08:00
} $ else $ if V is $ map {
2025-04-13 16:32:13 +08:00
v := decode_map ( mut s , V { } ) !
m [ k as K ] = v
} $ else $ if V is $ string {
v := decode_string ( mut s ) !
m [ k as K ] = v
2025-04-02 14:05:22 +08:00
} $ else {
2025-04-13 16:32:13 +08:00
v := decode_primitive ( mut s , unsafe { V ( 0 ) } ) !
m [ k as K ] = v
2025-04-02 14:05:22 +08:00
}
}
return m
}
@ [ inline ]
fn ( mut s DecodeState ) get_u64 ( ) ! u64 {
if s . offset + 8 > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : b y t e s l e n g t h i s n o t e n o u g h f o r u 6 4 ' )
2025-04-02 14:05:22 +08:00
}
defer {
s . offset += 8
}
if s . big_endian {
return big_endian_u64_at ( s . b , s . offset )
} else {
return little_endian_u64_at ( s . b , s . offset )
}
}
@ [ inline ]
fn ( mut s DecodeState ) get_u32 ( ) ! u32 {
if s . offset + 4 > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : b y t e s l e n g t h i s n o t e n o u g h f o r u 3 2 ' )
2025-04-02 14:05:22 +08:00
}
defer {
s . offset += 4
}
if s . big_endian {
return big_endian_u32_at ( s . b , s . offset )
} else {
return little_endian_u32_at ( s . b , s . offset )
}
}
@ [ inline ]
fn ( mut s DecodeState ) get_u16 ( ) ! u16 {
if s . offset + 2 > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : b y t e s l e n g t h i s n o t e n o u g h f o r u 1 6 ' )
2025-04-02 14:05:22 +08:00
}
defer {
s . offset += 2
}
if s . big_endian {
return big_endian_u16_at ( s . b , s . offset )
} else {
return little_endian_u16_at ( s . b , s . offset )
}
}
@ [ inline ]
fn ( mut s DecodeState ) get_u8 ( ) ! u8 {
if s . offset + 1 > s . b . len {
2025-04-13 16:32:13 +08:00
return error ( ' $ { @FN } ( ) : b y t e s l e n g t h i s n o t e n o u g h f o r u 8 ' )
2025-04-02 14:05:22 +08:00
}
defer {
s . offset += 1
}
return s . b [ s . offset ]
}
@ [ inline ]
fn ( mut s EncodeState ) put_u64 ( value u64 ) {
if s . big_endian {
big_endian_put_u64 ( mut s . b8 , value )
} else {
little_endian_put_u64 ( mut s . b8 , value )
}
s . b << s . b8
}
@ [ inline ]
fn ( mut s EncodeState ) put_u32 ( value u32 ) {
if s . big_endian {
big_endian_put_u32 ( mut s . b4 , value )
} else {
little_endian_put_u32 ( mut s . b4 , value )
}
s . b << s . b4
}
@ [ inline ]
fn ( mut s EncodeState ) put_u16 ( value u16 ) {
if s . big_endian {
big_endian_put_u16 ( mut s . b2 , value )
} else {
little_endian_put_u16 ( mut s . b2 , value )
}
s . b << s . b2
}
@ [ inline ]
fn ( mut s EncodeState ) put_u8 ( value u8 ) {
s . b << value
}