the base
This commit is contained in:
115
lib/data/encoder/auto.v
Normal file
115
lib/data/encoder/auto.v
Normal file
@@ -0,0 +1,115 @@
|
||||
module encoder
|
||||
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// example see https://github.com/vlang/v/blob/master/examples/compiletime/reflection.v
|
||||
|
||||
pub fn encode[T](obj T) ![]u8 {
|
||||
mut d := new()
|
||||
// compile-time `for` loop
|
||||
// T.fields gives an array of a field metadata type
|
||||
$for field in T.fields {
|
||||
// Primitive types
|
||||
$if field.typ is string {
|
||||
// $(string_expr) produces an identifier
|
||||
d.add_string(obj.$(field.name).str())
|
||||
} $else $if field.typ is int {
|
||||
d.add_int(int(obj.$(field.name)))
|
||||
} $else $if field.typ is u8 {
|
||||
d.add_u8(u8(obj.$(field.name)))
|
||||
} $else $if field.typ is u16 {
|
||||
d.add_u16(u16(obj.$(field.name)))
|
||||
} $else $if field.typ is u32 {
|
||||
d.add_u32(u32(obj.$(field.name)))
|
||||
} $else $if field.typ is u64 {
|
||||
d.add_u64(u64(obj.$(field.name)))
|
||||
} $else $if field.typ is time.Time {
|
||||
d.add_time(time.new(obj.$(field.name)))
|
||||
// Arrays of primitive types
|
||||
} $else $if field.typ is []string {
|
||||
// d.add_list_string(obj.$(field.name)) why error??
|
||||
d.add_list_string(obj.$(field.name)[..])
|
||||
} $else $if field.typ is []int {
|
||||
d.add_list_int(obj.$(field.name)[..])
|
||||
} $else $if field.typ is []u8 {
|
||||
d.add_list_u8(obj.$(field.name)[..])
|
||||
} $else $if field.typ is []u16 {
|
||||
d.add_list_u16(obj.$(field.name)[..])
|
||||
} $else $if field.typ is []u32 {
|
||||
d.add_list_u32(obj.$(field.name)[..])
|
||||
} $else $if field.typ is []u64 {
|
||||
d.add_list_u64(obj.$(field.name)[..])
|
||||
// Maps of primitive types
|
||||
} $else $if field.typ is map[string]string {
|
||||
d.add_map_string(obj.$(field.name).clone())
|
||||
} $else $if field.typ is map[string][]u8 {
|
||||
d.add_map_bytes(obj.$(field.name).clone())
|
||||
// Structs
|
||||
} $else $if field.is_struct {
|
||||
e := encode(obj.$(field.name))!
|
||||
d.add_list_u8(e)
|
||||
} $else {
|
||||
typ_name := typeof(obj.$(field.name)).name
|
||||
return error("The type `${typ_name}` of field `${field.name}` can't be encoded")
|
||||
}
|
||||
}
|
||||
return d.data
|
||||
}
|
||||
|
||||
pub fn decode[T](data []u8) !T {
|
||||
mut d := decoder_new(data)
|
||||
mut result := T{}
|
||||
// compile-time `for` loop
|
||||
// T.fields gives an array of a field metadata type
|
||||
$for field in T.fields {
|
||||
// console.print_debug(field.name)
|
||||
// console.print_debug(typeof(result.$(field.name)).name)
|
||||
// console.print_debug(result.$(field.name))
|
||||
|
||||
// Primitive types
|
||||
$if field.typ is string {
|
||||
// $(string_expr) produces an identifier
|
||||
result.$(field.name) = d.get_string()
|
||||
} $else $if field.typ is int {
|
||||
result.$(field.name) = d.get_int()
|
||||
} $else $if field.typ is u8 {
|
||||
result.$(field.name) = d.get_u8()
|
||||
} $else $if field.typ is u16 {
|
||||
result.$(field.name) = d.get_u16()
|
||||
} $else $if field.typ is u32 {
|
||||
result.$(field.name) = d.get_u32()
|
||||
} $else $if field.typ is u64 {
|
||||
result.$(field.name) = d.get_u64()
|
||||
} $else $if field.typ is time.Time {
|
||||
result.$(field.name) = d.get_time()
|
||||
// Arrays of primitive types
|
||||
} $else $if field.typ is []string {
|
||||
result.$(field.name) = d.get_list_string()
|
||||
} $else $if field.typ is []int {
|
||||
result.$(field.name) = d.get_list_int()
|
||||
} $else $if field.typ is []u8 {
|
||||
result.$(field.name) = d.get_list_u8()
|
||||
} $else $if field.typ is []u16 {
|
||||
result.$(field.name) = d.get_list_u16()
|
||||
} $else $if field.typ is []u32 {
|
||||
result.$(field.name) = d.get_list_u32()
|
||||
} $else $if field.typ is []u64 {
|
||||
result.$(field.name) = d.get_list_u64()
|
||||
// Maps of primitive types
|
||||
} $else $if field.typ is map[string]string {
|
||||
result.$(field.name) = d.get_map_string()
|
||||
} $else $if field.typ is map[string][]u8 {
|
||||
result.$(field.name) = d.get_map_bytes()
|
||||
// Structs
|
||||
} $else $if field.is_struct {
|
||||
// TODO handle recursive behavior
|
||||
} $else {
|
||||
typ_name := typeof(result.$(field.name)).name
|
||||
return error("The type `${typ_name}` of field `${field.name}` can't be decoded")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO: complete, the recursive behavior will be little tricky
|
||||
155
lib/data/encoder/encoder_decode.v
Normal file
155
lib/data/encoder/encoder_decode.v
Normal file
@@ -0,0 +1,155 @@
|
||||
module encoder
|
||||
|
||||
import encoding.binary as bin
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import time
|
||||
|
||||
pub struct Decoder {
|
||||
pub mut:
|
||||
version u8 = 1 // is important
|
||||
data []u8
|
||||
}
|
||||
|
||||
pub fn decoder_new(data []u8) Decoder {
|
||||
mut e := Decoder{}
|
||||
e.data = data
|
||||
// e.data = data.reverse()
|
||||
return e
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_string() string {
|
||||
n := d.get_u16()
|
||||
v := d.data[..n]
|
||||
d.data.delete_many(0, n)
|
||||
return v.bytestr()
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_int() int {
|
||||
return int(d.get_u32())
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_bytes() []u8 {
|
||||
n := int(d.get_u32())
|
||||
v := d.data[..n]
|
||||
d.data.delete_many(0, n)
|
||||
return v
|
||||
}
|
||||
|
||||
// adds u16 length of string in bytes + the bytes
|
||||
pub fn (mut d Decoder) get_u8() u8 {
|
||||
// remove first byte, this corresponds to u8, so the data bytestring becomes 1 byte shorter
|
||||
v := d.data.first()
|
||||
d.data.delete(0)
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_u16() u16 {
|
||||
v := d.data[..2]
|
||||
d.data.delete_many(0, 2)
|
||||
return bin.little_endian_u16(v)
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_u32() u32 {
|
||||
v := d.data[..4]
|
||||
d.data.delete_many(0, 4)
|
||||
return bin.little_endian_u32(v)
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_u64() u64 {
|
||||
v := d.data[..8]
|
||||
d.data.delete_many(0, 8)
|
||||
return bin.little_endian_u64(v)
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_i64() i64 {
|
||||
v := d.data[..8]
|
||||
d.data.delete_many(0, 8)
|
||||
return u64(bin.little_endian_u64(v))
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_time() time.Time {
|
||||
nano_time := d.get_i64()
|
||||
seconds := nano_time / int(1e9)
|
||||
nano_seconds := int(nano_time % int(1e9))
|
||||
return time.unix_nanosecond(seconds, nano_seconds)
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_ourtime() ourtime.OurTime {
|
||||
return ourtime.OurTime{
|
||||
unixt: d.get_i64()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_list_string() []string {
|
||||
n := d.get_u16()
|
||||
mut v := []string{len: int(n)}
|
||||
for i in 0 .. n {
|
||||
v[i] = d.get_string()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_list_int() []int {
|
||||
n := d.get_u16()
|
||||
mut v := []int{len: int(n)}
|
||||
for i in 0 .. n {
|
||||
v[i] = d.get_int()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_list_u8() []u8 {
|
||||
n := d.get_u16()
|
||||
v := d.data[..n]
|
||||
d.data.delete_many(0, n)
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_list_u16() []u16 {
|
||||
n := d.get_u16()
|
||||
mut v := []u16{len: int(n)}
|
||||
for i in 0 .. n {
|
||||
v[i] = d.get_u16()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_list_u32() []u32 {
|
||||
n := d.get_u16()
|
||||
mut v := []u32{len: int(n)}
|
||||
for i in 0 .. n {
|
||||
v[i] = d.get_u32()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_list_u64() []u64 {
|
||||
n := d.get_u16()
|
||||
mut v := []u64{len: int(n)}
|
||||
for i in 0 .. n {
|
||||
v[i] = d.get_u64()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_map_string() map[string]string {
|
||||
n := d.get_u16()
|
||||
mut v := map[string]string{}
|
||||
for _ in 0 .. n {
|
||||
key := d.get_string()
|
||||
val := d.get_string()
|
||||
v[key] = val
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
pub fn (mut d Decoder) get_map_bytes() map[string][]u8 {
|
||||
n := d.get_u16()
|
||||
mut v := map[string][]u8{}
|
||||
for _ in 0 .. n {
|
||||
key := d.get_string()
|
||||
val := d.get_bytes()
|
||||
v[key] = val
|
||||
}
|
||||
return v
|
||||
}
|
||||
176
lib/data/encoder/encoder_encode.v
Normal file
176
lib/data/encoder/encoder_encode.v
Normal file
@@ -0,0 +1,176 @@
|
||||
module encoder
|
||||
|
||||
import time
|
||||
import encoding.binary as bin
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
|
||||
const kb = 1024
|
||||
|
||||
pub struct Encoder {
|
||||
pub mut:
|
||||
data []u8
|
||||
// datatypes []DataType
|
||||
}
|
||||
|
||||
// enum DataType{
|
||||
// string
|
||||
// int
|
||||
// bytes
|
||||
// u8
|
||||
// u16
|
||||
// u32
|
||||
// u64
|
||||
// time
|
||||
// list_string
|
||||
// list_int
|
||||
// list_u8
|
||||
// list_u16
|
||||
// list_u32
|
||||
// list_u64
|
||||
// map_string
|
||||
// map_bytes
|
||||
// }
|
||||
|
||||
pub fn new() Encoder {
|
||||
mut e := Encoder{}
|
||||
return e
|
||||
}
|
||||
|
||||
// adds u16 length of string in bytes + the bytes
|
||||
pub fn (mut b Encoder) add_string(data string) {
|
||||
if data.len > 64 * kb {
|
||||
panic('string cannot be bigger than 64kb')
|
||||
}
|
||||
b.add_u16(u16(data.len))
|
||||
b.data << data.bytes()
|
||||
}
|
||||
|
||||
// Please note that unlike C and Go, int is always a 32 bit integer.
|
||||
// We borrow the add_u32() function to handle the encoding of a 32 bit type
|
||||
pub fn (mut b Encoder) add_int(data int) {
|
||||
b.add_u32(u32(data))
|
||||
}
|
||||
|
||||
// add bytes or bytestring
|
||||
pub fn (mut b Encoder) add_bytes(data []u8) {
|
||||
b.add_u32(u32(data.len))
|
||||
b.data << data
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_u8(data u8) {
|
||||
b.data << data
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_u16(data u16) {
|
||||
mut d := []u8{len: 2}
|
||||
bin.little_endian_put_u16(mut d, data)
|
||||
b.data << d
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_u32(data u32) {
|
||||
mut d := []u8{len: 4}
|
||||
bin.little_endian_put_u32(mut d, data)
|
||||
b.data << d
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_u64(data u64) {
|
||||
mut d := []u8{len: 8}
|
||||
bin.little_endian_put_u64(mut d, data)
|
||||
b.data << d
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_i64(data i64) {
|
||||
mut d := []u8{len: 8}
|
||||
bin.little_endian_put_u64(mut d, u64(data))
|
||||
b.data << d
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_time(data time.Time) {
|
||||
b.add_u64(u64(data.unix_nano())) // add as epoch time
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_ourtime(data ourtime.OurTime) {
|
||||
b.add_i64(data.unixt)
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_list_string(data []string) {
|
||||
if data.len > 64 * kb {
|
||||
panic('list cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len))
|
||||
for item in data {
|
||||
b.add_string(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_list_int(data []int) {
|
||||
if data.len > 64 * kb {
|
||||
panic('list cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // how many items in list
|
||||
for item in data {
|
||||
b.add_int(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_list_u8(data []u8) {
|
||||
if data.len > 64 * kb {
|
||||
panic('list cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // how many items in list
|
||||
b.data << data
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_list_u16(data []u16) {
|
||||
if data.len > 64 * kb {
|
||||
panic('list cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // how many items in list
|
||||
for item in data {
|
||||
b.add_u16(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_list_u32(data []u32) {
|
||||
if data.len > 64 * kb {
|
||||
panic('list cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // how many items in list
|
||||
for item in data {
|
||||
b.add_u32(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b Encoder) add_list_u64(data []u64) {
|
||||
if data.len > 64 * kb {
|
||||
panic('list cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // how many items in list
|
||||
for item in data {
|
||||
b.add_u64(item)
|
||||
}
|
||||
}
|
||||
|
||||
// when complicated hash e.g. map of other object need to serialize each sub object
|
||||
pub fn (mut b Encoder) add_map_string(data map[string]string) {
|
||||
if data.len > 64 * kb {
|
||||
panic('map cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // max nr of items in the map
|
||||
for key, val in data {
|
||||
b.add_string(key)
|
||||
b.add_string(val)
|
||||
}
|
||||
}
|
||||
|
||||
// when complicated hash e.g. map of other object need to serialize each sub object
|
||||
pub fn (mut b Encoder) add_map_bytes(data map[string][]u8) {
|
||||
if data.len > 64 * kb {
|
||||
panic('map cannot have more than 64kb items.')
|
||||
}
|
||||
b.add_u16(u16(data.len)) // max nr of items in the map
|
||||
for key, val in data {
|
||||
b.add_string(key)
|
||||
b.add_bytes(val)
|
||||
}
|
||||
}
|
||||
285
lib/data/encoder/encoder_test.v
Normal file
285
lib/data/encoder/encoder_test.v
Normal file
@@ -0,0 +1,285 @@
|
||||
module encoder
|
||||
|
||||
import time
|
||||
import math
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn test_string() {
|
||||
mut e := new()
|
||||
e.add_string('a')
|
||||
e.add_string('bc')
|
||||
assert e.data == [u8(1), 0, 97, 2, 0, 98, 99]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_string() == 'a'
|
||||
assert d.get_string() == 'bc'
|
||||
}
|
||||
|
||||
fn test_int() {
|
||||
mut e := new()
|
||||
e.add_int(min_i32)
|
||||
e.add_int(max_i32)
|
||||
assert e.data == [u8(0x00), 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x7f]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_int() == min_i32
|
||||
assert d.get_int() == max_i32
|
||||
}
|
||||
|
||||
fn test_bytes() {
|
||||
sb := 'abcdef'.bytes()
|
||||
|
||||
mut e := new()
|
||||
e.add_list_u8(sb)
|
||||
assert e.data == [u8(6), 0, 97, 98, 99, 100, 101, 102]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_list_u8() == sb
|
||||
}
|
||||
|
||||
fn test_u8() {
|
||||
mut e := new()
|
||||
e.add_u8(min_u8)
|
||||
e.add_u8(max_u8)
|
||||
assert e.data == [u8(0x00), 0xff]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_u8() == min_u8
|
||||
assert d.get_u8() == max_u8
|
||||
}
|
||||
|
||||
fn test_u16() {
|
||||
mut e := new()
|
||||
e.add_u16(min_u16)
|
||||
e.add_u16(max_u16)
|
||||
assert e.data == [u8(0x00), 0x00, 0xff, 0xff]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_u16() == min_u16
|
||||
assert d.get_u16() == max_u16
|
||||
}
|
||||
|
||||
fn test_u32() {
|
||||
mut e := new()
|
||||
e.add_u32(min_u32)
|
||||
e.add_u32(max_u32)
|
||||
assert e.data == [u8(0x00), 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_u32() == min_u32
|
||||
assert d.get_u32() == max_u32
|
||||
}
|
||||
|
||||
fn test_u64() {
|
||||
mut e := new()
|
||||
e.add_u64(min_u64)
|
||||
e.add_u64(max_u64)
|
||||
assert e.data == [u8(0x00), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_u64() == min_u64
|
||||
assert d.get_u64() == max_u64
|
||||
}
|
||||
|
||||
fn test_time() {
|
||||
mut e := new()
|
||||
t := time.now()
|
||||
e.add_time(t)
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_time() == t
|
||||
}
|
||||
|
||||
fn test_list_string() {
|
||||
list := ['a', 'bc', 'def']
|
||||
|
||||
mut e := new()
|
||||
e.add_list_string(list)
|
||||
assert e.data == [u8(3), 0, 1, 0, 97, 2, 0, 98, 99, 3, 0, 100, 101, 102]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_list_string() == list
|
||||
}
|
||||
|
||||
fn test_list_int() {
|
||||
list := [0x872fea95, 0, 0xfdf2e68f]
|
||||
|
||||
mut e := new()
|
||||
e.add_list_int(list)
|
||||
assert e.data == [u8(3), 0, 0x95, 0xea, 0x2f, 0x87, 0, 0, 0, 0, 0x8f, 0xe6, 0xf2, 0xfd]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_list_int() == list
|
||||
}
|
||||
|
||||
fn test_list_u8() {
|
||||
list := [u8(153), 0, 22]
|
||||
|
||||
mut e := new()
|
||||
e.add_list_u8(list)
|
||||
assert e.data == [u8(3), 0, 153, 0, 22]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_list_u8() == list
|
||||
}
|
||||
|
||||
fn test_list_u16() {
|
||||
list := [u16(0x8725), 0, 0xfdff]
|
||||
|
||||
mut e := new()
|
||||
e.add_list_u16(list)
|
||||
assert e.data == [u8(3), 0, 0x25, 0x87, 0, 0, 0xff, 0xfd]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_list_u16() == list
|
||||
}
|
||||
|
||||
fn test_list_u32() {
|
||||
list := [u32(0x872fea95), 0, 0xfdf2e68f]
|
||||
|
||||
mut e := new()
|
||||
e.add_list_u32(list)
|
||||
assert e.data == [u8(3), 0, 0x95, 0xea, 0x2f, 0x87, 0, 0, 0, 0, 0x8f, 0xe6, 0xf2, 0xfd]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_list_u32() == list
|
||||
}
|
||||
|
||||
fn test_map_string() {
|
||||
mp := {
|
||||
'1': 'a'
|
||||
'2': 'bc'
|
||||
}
|
||||
|
||||
mut e := new()
|
||||
e.add_map_string(mp)
|
||||
assert e.data == [u8(2), 0, 1, 0, 49, 1, 0, 97, 1, 0, 50, 2, 0, 98, 99]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_map_string() == mp
|
||||
}
|
||||
|
||||
fn test_map_bytes() {
|
||||
mp := {
|
||||
'1': 'a'.bytes()
|
||||
'2': 'bc'.bytes()
|
||||
}
|
||||
|
||||
mut e := new()
|
||||
e.add_map_bytes(mp)
|
||||
assert e.data == [u8(2), 0, 1, 0, 49, 1, 0, 0, 0, 97, 1, 0, 50, 2, 0, 0, 0, 98, 99]
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
assert d.get_map_bytes() == mp
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
fn get_empty_struct_input[T]() StructType[T] {
|
||||
return StructType[T]{}
|
||||
}
|
||||
|
||||
fn get_struct_input[T](val T) StructType[T] {
|
||||
return StructType[T]{
|
||||
val: val
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_decode_struct[T](input StructType[T]) bool {
|
||||
data := encode(input) or {
|
||||
console.print_debug('Failed to encode, error: ${err}')
|
||||
return false
|
||||
}
|
||||
output := decode[StructType[T]](data) or {
|
||||
console.print_debug('Failed to decode, error: ${err}')
|
||||
return false
|
||||
}
|
||||
return input == output
|
||||
}
|
||||
|
||||
fn test_struct() {
|
||||
// string
|
||||
assert encode_decode_struct(get_empty_struct_input[string]())
|
||||
assert encode_decode_struct(get_struct_input(''))
|
||||
assert encode_decode_struct(get_struct_input('a'))
|
||||
|
||||
// int
|
||||
assert encode_decode_struct(get_empty_struct_input[int]())
|
||||
assert encode_decode_struct(get_struct_input(-1))
|
||||
|
||||
// u8
|
||||
assert encode_decode_struct(get_empty_struct_input[u8]())
|
||||
assert encode_decode_struct(get_struct_input(u8(2)))
|
||||
|
||||
// u16
|
||||
assert encode_decode_struct(get_empty_struct_input[u16]())
|
||||
assert encode_decode_struct(get_struct_input(u16(3)))
|
||||
|
||||
// u32
|
||||
assert encode_decode_struct(get_empty_struct_input[u32]())
|
||||
assert encode_decode_struct(get_struct_input(u32(4)))
|
||||
|
||||
// u64
|
||||
assert encode_decode_struct(get_empty_struct_input[u64]())
|
||||
assert encode_decode_struct(get_struct_input(u64(5)))
|
||||
|
||||
// time.Time
|
||||
// assert encode_decode_struct[time.Time](get_empty_struct_input[time.Time]()) // get error here
|
||||
assert encode_decode_struct[time.Time](get_struct_input[time.Time](time.now()))
|
||||
|
||||
// string array
|
||||
assert encode_decode_struct(get_empty_struct_input[[]string]())
|
||||
assert encode_decode_struct(get_struct_input([]string{}))
|
||||
assert encode_decode_struct(get_struct_input(['']))
|
||||
assert encode_decode_struct(get_struct_input(['a']))
|
||||
|
||||
// int array
|
||||
assert encode_decode_struct(get_empty_struct_input[[]int]())
|
||||
assert encode_decode_struct(get_struct_input([]int{}))
|
||||
assert encode_decode_struct(get_struct_input([-1]))
|
||||
|
||||
// u8 array
|
||||
assert encode_decode_struct(get_empty_struct_input[[]u8]())
|
||||
assert encode_decode_struct(get_struct_input([]u8{}))
|
||||
assert encode_decode_struct(get_struct_input([u8(2)]))
|
||||
|
||||
// u16 array
|
||||
assert encode_decode_struct(get_empty_struct_input[[]u16]())
|
||||
assert encode_decode_struct(get_struct_input([]u16{}))
|
||||
assert encode_decode_struct(get_struct_input([u16(3)]))
|
||||
|
||||
// u32 array
|
||||
assert encode_decode_struct(get_empty_struct_input[[]u32]())
|
||||
assert encode_decode_struct(get_struct_input([]u32{}))
|
||||
assert encode_decode_struct(get_struct_input([u32(4)]))
|
||||
|
||||
// u64 array
|
||||
assert encode_decode_struct(get_empty_struct_input[[]u64]())
|
||||
assert encode_decode_struct(get_struct_input([]u64{}))
|
||||
assert encode_decode_struct(get_struct_input([u64(5)]))
|
||||
|
||||
// string map
|
||||
assert encode_decode_struct(get_empty_struct_input[map[string]string]())
|
||||
assert encode_decode_struct(get_struct_input(map[string]string{}))
|
||||
assert encode_decode_struct(get_struct_input({
|
||||
'1': 'a'
|
||||
}))
|
||||
|
||||
// bytes map
|
||||
assert encode_decode_struct(get_empty_struct_input[map[string][]u8]())
|
||||
assert encode_decode_struct(get_struct_input(map[string][]u8{}))
|
||||
assert encode_decode_struct(get_struct_input({
|
||||
'1': 'a'.bytes()
|
||||
}))
|
||||
|
||||
// struct
|
||||
assert encode_decode_struct(get_empty_struct_input[StructType[int]]())
|
||||
assert encode_decode_struct(get_struct_input(StructType[int]{}))
|
||||
// assert encode_decode_struct(get_struct_input(StructType[int]{
|
||||
// val: int(1)
|
||||
// })) // decode not implemented
|
||||
}
|
||||
247
lib/data/encoder/readme.md
Normal file
247
lib/data/encoder/readme.md
Normal file
@@ -0,0 +1,247 @@
|
||||
|
||||
# V Binary Encoder/Decoder
|
||||
|
||||
A high-performance binary encoder/decoder module for V that provides efficient serialization and deserialization of data structures. The encoder supports automatic encoding/decoding of structs using V's compile-time reflection capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- Automatic struct encoding/decoding using compile-time reflection
|
||||
- Support for primitive types, arrays, maps, and nested structs
|
||||
- Compact binary format with length prefixing
|
||||
- Size limits to prevent memory issues (64KB for strings/lists)
|
||||
- Comprehensive error handling
|
||||
- Built-in versioning support
|
||||
|
||||
## Format
|
||||
|
||||
The binary format starts with a version byte (currently v1), followed by the encoded data:
|
||||
|
||||
```
|
||||
[version_byte][encoded_data...]
|
||||
```
|
||||
|
||||
## Supported Types
|
||||
|
||||
### Primitive Types
|
||||
- `string`
|
||||
- `int` (32-bit)
|
||||
- `u8`
|
||||
- `u16`
|
||||
- `u32`
|
||||
- `u64`
|
||||
- `time.Time`
|
||||
|
||||
### Arrays
|
||||
- `[]string`
|
||||
- `[]int`
|
||||
- `[]u8`
|
||||
- `[]u16`
|
||||
- `[]u32`
|
||||
- `[]u64`
|
||||
|
||||
### Maps
|
||||
- `map[string]string`
|
||||
- `map[string][]u8`
|
||||
|
||||
### Structs
|
||||
- Nested struct support with automatic encoding/decoding
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Encoding
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
// Create a new encoder
|
||||
mut e := encoder.new()
|
||||
|
||||
// Add primitive values
|
||||
e.add_string('hello')
|
||||
e.add_int(42)
|
||||
e.add_u8(255)
|
||||
e.add_u16(65535)
|
||||
e.add_u32(4294967295)
|
||||
e.add_u64(18446744073709551615)
|
||||
|
||||
// Add arrays
|
||||
e.add_list_string(['one', 'two', 'three'])
|
||||
e.add_list_int([1, 2, 3])
|
||||
|
||||
// Add maps
|
||||
e.add_map_string({
|
||||
'key1': 'value1'
|
||||
'key2': 'value2'
|
||||
})
|
||||
|
||||
// Get encoded bytes
|
||||
encoded := e.data
|
||||
```
|
||||
|
||||
### Basic Decoding
|
||||
|
||||
```v
|
||||
// Create decoder from bytes
|
||||
mut d := encoder.decoder_new(encoded)
|
||||
|
||||
// Read values in same order as encoded
|
||||
str := d.get_string()
|
||||
num := d.get_int()
|
||||
byte := d.get_u8()
|
||||
u16_val := d.get_u16()
|
||||
u32_val := d.get_u32()
|
||||
u64_val := d.get_u64()
|
||||
|
||||
// Read arrays
|
||||
strings := d.get_list_string()
|
||||
ints := d.get_list_int()
|
||||
|
||||
// Read maps
|
||||
str_map := d.get_map_string()
|
||||
```
|
||||
|
||||
### Automatic Struct Encoding/Decoding
|
||||
|
||||
```v
|
||||
struct Person {
|
||||
name string
|
||||
age int
|
||||
tags []string
|
||||
meta map[string]string
|
||||
}
|
||||
|
||||
// Create struct instance
|
||||
person := Person{
|
||||
name: 'John'
|
||||
age: 30
|
||||
tags: ['developer', 'v']
|
||||
meta: {
|
||||
'location': 'NYC'
|
||||
'role': 'engineer'
|
||||
}
|
||||
}
|
||||
|
||||
// Encode struct
|
||||
encoded := encoder.encode(person)!
|
||||
|
||||
// Decode back to struct
|
||||
decoded := encoder.decode[Person](encoded)!
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Here's a complete example showing how to encode nested structs:
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
// Define some nested structs
|
||||
struct Address {
|
||||
street string
|
||||
number int
|
||||
country string
|
||||
}
|
||||
|
||||
struct Person {
|
||||
name string
|
||||
age int
|
||||
addresses []Address // nested array of structs
|
||||
metadata map[string]string
|
||||
}
|
||||
|
||||
// Example usage
|
||||
fn main() {
|
||||
// Create test data
|
||||
mut person := Person{
|
||||
name: 'John Doe'
|
||||
age: 30
|
||||
addresses: [
|
||||
Address{
|
||||
street: 'Main St'
|
||||
number: 123
|
||||
country: 'USA'
|
||||
},
|
||||
Address{
|
||||
street: 'Side St'
|
||||
number: 456
|
||||
country: 'Canada'
|
||||
}
|
||||
]
|
||||
metadata: {
|
||||
'id': 'abc123'
|
||||
'type': 'customer'
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the data
|
||||
mut e := encoder.new()
|
||||
|
||||
// Add version byte (v1)
|
||||
e.add_u8(1)
|
||||
|
||||
// Encode the Person struct
|
||||
e.add_string(person.name)
|
||||
e.add_int(person.age)
|
||||
|
||||
// Encode the addresses array
|
||||
e.add_u16(u16(person.addresses.len)) // number of addresses
|
||||
for addr in person.addresses {
|
||||
e.add_string(addr.street)
|
||||
e.add_int(addr.number)
|
||||
e.add_string(addr.country)
|
||||
}
|
||||
|
||||
// Encode the metadata map
|
||||
e.add_map_string(person.metadata)
|
||||
|
||||
// The binary data is now in e.data
|
||||
encoded := e.data
|
||||
|
||||
// Later, when decoding, first byte tells us the version
|
||||
version := encoded[0]
|
||||
assert version == 1
|
||||
}
|
||||
```
|
||||
|
||||
## Binary Format Details
|
||||
|
||||
For the example above, the binary layout would be:
|
||||
|
||||
```
|
||||
[1] // version byte (v1)
|
||||
[len][John Doe] // name (u16 length + bytes)
|
||||
[30] // age (int/u32)
|
||||
[2] // number of addresses (u16)
|
||||
[len][Main St] // address 1 street
|
||||
[123] // address 1 number
|
||||
[len][USA] // address 1 country
|
||||
[len][Side St] // address 2 street
|
||||
[456] // address 2 number
|
||||
[len][Canada] // address 2 country
|
||||
[2] // number of metadata entries (u16)
|
||||
[len][id] // key 1
|
||||
[len][abc123] // value 1
|
||||
[len][type] // key 2
|
||||
[len][customer] // value 2
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Binary Format
|
||||
|
||||
The encoded data follows this format:
|
||||
|
||||
1. For strings:
|
||||
- u16 length prefix
|
||||
- raw string bytes
|
||||
|
||||
2. For arrays:
|
||||
- u16 length prefix
|
||||
- encoded elements
|
||||
|
||||
3. For maps:
|
||||
- u16 count of entries
|
||||
- encoded key-value pairs
|
||||
|
||||
Reference in New Issue
Block a user