This commit is contained in:
2025-03-29 11:13:16 +01:00
parent 37c17fc7da
commit 45ed369a78
16 changed files with 2066 additions and 20 deletions

View File

@@ -2,7 +2,9 @@ module encoder
import encoding.binary as bin
import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.data.currency
import time
import freeflowuniverse.herolib.data.gid
pub struct Decoder {
pub mut:
@@ -127,6 +129,14 @@ pub fn (mut d Decoder) get_i64() !i64 {
return u64(bin.little_endian_u64(bytes))
}
pub fn (mut d Decoder) get_f64() !f64 {
// Get the u64 bits first and then convert back to f64
bits := d.get_u64()!
// Use unsafe to convert bits to f64
f := unsafe { *(&f64(&bits)) }
return f
}
pub fn (mut d Decoder) get_time() !time.Time {
secs_ := d.get_u32()!
secs := i64(secs_)
@@ -139,6 +149,14 @@ pub fn (mut d Decoder) get_ourtime() !ourtime.OurTime {
}
}
pub fn (mut d Decoder) get_percentage() !u8 {
val := d.get_u8()!
if val > 100 {
return error('percentage value ${val} exceeds 100')
}
return val
}
pub fn (mut d Decoder) get_list_string() ![]string {
n := d.get_u16()!
mut v := []string{len: int(n)}
@@ -221,3 +239,18 @@ pub fn (mut d Decoder) get_map_bytes() !map[string][]u8 {
}
return v
}
// Gets GID from encoded string
pub fn (mut d Decoder) get_gid() !gid.GID {
gid_str := d.get_string()!
return gid.new(gid_str)
}
pub fn (mut d Decoder) get_currency() !currency.Amount {
n := d.get_string()!
v := d.get_f64()!
return currency.Amount{
currency: currency.get(n)!
val: v
}
}

View File

@@ -3,6 +3,8 @@ module encoder
import time
import encoding.binary as bin
import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.data.currency
import freeflowuniverse.herolib.data.gid
const kb = 1024
@@ -101,6 +103,32 @@ pub fn (mut b Encoder) add_ourtime(data ourtime.OurTime) {
b.add_u32(u32(data.unixt))
}
// adds a float64 value
pub fn (mut b Encoder) add_f64(data f64) {
// Convert f64 to bits first, then store as u64
bits := unsafe { *(&u64(&data)) }
b.add_u64(bits)
}
// adds currency.Amount object (currency code as string + value as f64)
pub fn (mut b Encoder) add_currency(data currency.Amount) {
// Add currency code as string
b.add_string(data.currency.name)
b.add_f64(data.val)
}
// adds gid as a string
pub fn (mut b Encoder) add_gid(data gid.GID) {
b.add_string(data.str())
}
pub fn (mut b Encoder) add_percentage(data u8) {
if data > 100 {
panic('percentage cannot be greater than 100')
}
b.add_u8(data)
}
pub fn (mut b Encoder) add_list_string(data []string) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')

View File

@@ -3,6 +3,8 @@ module encoder
import time
import math
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.gid
import freeflowuniverse.herolib.data.currency
fn test_string() {
mut e := new()
@@ -186,6 +188,103 @@ fn test_map_bytes() {
assert d.get_map_bytes()! == mp
}
fn test_gid() {
// Test with a standard GID
mut e := new()
mut g1 := gid.new("myproject:123")!
e.add_gid(g1)
// Test with a GID that has a default circle name
mut g2 := gid.new_from_parts("", 999)!
e.add_gid(g2)
// Test with a GID that has spaces before fixing
mut g3 := gid.new("project1:456")!
e.add_gid(g3)
mut d := decoder_new(e.data)
assert d.get_gid()!.str() == g1.str()
assert d.get_gid()!.str() == g2.str()
assert d.get_gid()!.str() == g3.str()
}
fn test_currency() {
// Create USD currency manually
mut usd_curr := currency.Currency{
name: 'USD'
usdval: 1.0
}
// Create EUR currency manually
mut eur_curr := currency.Currency{
name: 'EUR'
usdval: 1.1
}
// Create Bitcoin currency manually
mut btc_curr := currency.Currency{
name: 'BTC'
usdval: 60000.0
}
// Create TFT currency manually
mut tft_curr := currency.Currency{
name: 'TFT'
usdval: 0.05
}
// Create currency amounts
mut usd_amount := currency.Amount{
currency: usd_curr
val: 1.5
}
mut eur_amount := currency.Amount{
currency: eur_curr
val: 100.0
}
mut btc_amount := currency.Amount{
currency: btc_curr
val: 0.01
}
mut tft_amount := currency.Amount{
currency: tft_curr
val: 1000.0
}
mut e := new()
e.add_currency(usd_amount)
e.add_currency(eur_amount)
e.add_currency(btc_amount)
e.add_currency(tft_amount)
mut d := decoder_new(e.data)
// Override the currency.get function by manually checking currency names
// since we can't rely on the global currency functions for testing
mut decoded_curr1 := d.get_string()!
mut decoded_val1 := d.get_f64()!
assert decoded_curr1 == 'USD'
assert math.abs(decoded_val1 - 1.5) < 0.00001
mut decoded_curr2 := d.get_string()!
mut decoded_val2 := d.get_f64()!
assert decoded_curr2 == 'EUR'
assert math.abs(decoded_val2 - 100.0) < 0.00001
mut decoded_curr3 := d.get_string()!
mut decoded_val3 := d.get_f64()!
assert decoded_curr3 == 'BTC'
assert math.abs(decoded_val3 - 0.01) < 0.00001
mut decoded_curr4 := d.get_string()!
mut decoded_val4 := d.get_f64()!
assert decoded_curr4 == 'TFT'
assert math.abs(decoded_val4 - 1000.0) < 0.00001
}
struct StructType[T] {
mut:
val T

View File

@@ -27,12 +27,19 @@ The binary format starts with a version byte (currently v1), followed by the enc
### Primitive Types
- `string`
- `int` (32-bit)
- `i64` (64-bit integer)
- `f64` (64-bit float)
- `bool`
- `u8`
- `u16`
- `u32`
- `u64`
- `time.Time`
- `ourtime.OurTime` (native support)
- `percentage` (u8 between 0-100)
- `currency.Amount` (currency amount with value)
- `gid.GID` (Global ID)
- `[]byte` (raw byte arrays)
### Arrays
- `[]string`
@@ -68,15 +75,58 @@ e.add_u16(65535)
e.add_u32(4294967295)
e.add_u64(18446744073709551615)
// Add percentage (u8 between 0-100)
e.add_percentage(75)
// Add float64 value
e.add_f64(3.14159)
// Add int64 value
e.add_i64(-9223372036854775807)
// Add raw bytes
e.add_bytes('raw data'.bytes())
// Add time value
import time
e.add_time(time.now())
// Add OurTime (native time format)
import freeflowuniverse.herolib.data.ourtime
my_time := ourtime.OurTime.now()
e.add_ourtime(my_time)
// Add GID
import freeflowuniverse.herolib.data.gid
my_gid := gid.new('project:123')!
e.add_gid(my_gid)
// Add currency amount
import freeflowuniverse.herolib.data.currency
usd := currency.get('USD')!
amount := currency.Amount{
currency: usd
val: 99.95
}
e.add_currency(amount)
// Add arrays
e.add_list_string(['one', 'two', 'three'])
e.add_list_int([1, 2, 3])
e.add_list_u8([u8(1), 2, 3])
e.add_list_u16([u16(1), 2, 3])
e.add_list_u32([u32(1), 2, 3])
e.add_list_u64([u64(1), 2, 3])
// Add maps
e.add_map_string({
'key1': 'value1'
'key2': 'value2'
})
e.add_map_bytes({
'key1': 'value1'.bytes()
'key2': 'value2'.bytes()
})
// Get encoded bytes
encoded := e.data
@@ -89,20 +139,53 @@ encoded := e.data
mut d := encoder.decoder_new(encoded)
// Read values in same order as encoded
str := d.get_string()
num := d.get_int()
bool_val := d.get_bool()
byte := d.get_u8()
u16_val := d.get_u16()
u32_val := d.get_u32()
u64_val := d.get_u64()
str := d.get_string()!
num := d.get_int()!
bool_val := d.get_bool()!
byte := d.get_u8()!
u16_val := d.get_u16()!
u32_val := d.get_u32()!
u64_val := d.get_u64()!
// Read percentage value
percentage := d.get_percentage()! // u8 value between 0-100
// Read float64 value
f64_val := d.get_f64()!
// Read int64 value
i64_val := d.get_i64()!
// Read raw bytes
bytes_data := d.get_bytes()!
// Read time value
import time
time_val := d.get_time()!
// Read OurTime value
import freeflowuniverse.herolib.data.ourtime
my_time := d.get_ourtime()!
// Read GID
import freeflowuniverse.herolib.data.gid
my_gid := d.get_gid()!
// Read currency amount
import freeflowuniverse.herolib.data.currency
amount := d.get_currency()!
// Read arrays
strings := d.get_list_string()
ints := d.get_list_int()
strings := d.get_list_string()!
ints := d.get_list_int()!
bytes_list := d.get_list_u8()!
u16_list := d.get_list_u16()!
u32_list := d.get_list_u32()!
u64_list := d.get_list_u64()!
// Read maps
str_map := d.get_map_string()
str_map := d.get_map_string()!
bytes_map := d.get_map_bytes()!
```
### Automatic Struct Encoding/Decoding
@@ -236,17 +319,39 @@ For the example above, the binary layout would be:
### Binary Format
The encoded data follows this format:
The encoded data follows this format for different types:
1. For strings:
- u16 length prefix
- raw string bytes
#### Primitive Types
- `string`: u16 length prefix + raw string bytes
- `int` (32-bit): 4 bytes in little-endian format
- `i64` (64-bit): 8 bytes in little-endian format
- `f64`: 8 bytes (IEEE-754 double precision) in little-endian format
- `bool`: Single byte (1 for true, 0 for false)
- `u8`: Single byte
- `u16`: 2 bytes in little-endian format
- `u32`: 4 bytes in little-endian format
- `u64`: 8 bytes in little-endian format
- `percentage`: Single byte (0-100)
2. For arrays:
- u16 length prefix
- encoded elements
#### Special Types
- `time.Time`: Encoded as u32 Unix timestamp (seconds since epoch)
- `ourtime.OurTime`: Encoded as u32 Unix timestamp
- `gid.GID`: Encoded as string in format "circle:id"
- `currency.Amount`: Encoded as a string (currency name) followed by f64 (value)
- `[]byte` (raw byte arrays): u32 length prefix + raw bytes
3. For maps:
- u16 count of entries
- encoded key-value pairs
#### Collections
- Arrays (`[]T`):
- u16 length prefix (number of elements)
- Each element encoded according to its type
- Maps:
- u16 count of entries
- For each entry:
- Key encoded according to its type
- Value encoded according to its type
### Size Limits
- Strings and arrays are limited to 64KB in length (u16 max)
- This limit helps prevent memory issues and ensures efficient processing