Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 122 additions & 19 deletions access/generic_decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ func DecodePrimitive(typ types.Type, buf []byte) (interface{}, error) {
}

// DecodeTupleGeneric: decode a []any from the current position in a SeqGetAccess.
func DecodeTupleGeneric(seq *SeqGetAccess, root bool) ([]any, error) {
// If root is true, the caller already consumed the tuple header.
// If ordered is true, maps inside the tuple are decoded as *types.OrderedMapAny.
func DecodeTupleGeneric(seq *SeqGetAccess, root bool, ordered bool) ([]any, error) {
nested := seq
pos := 0
if !root {

pos := seq.CurrentIndex()
typ, width, err := seq.PeekTypeWidth()
if err != nil {
Expand All @@ -82,57 +82,66 @@ func DecodeTupleGeneric(seq *SeqGetAccess, root bool) ([]any, error) {
}
return nil, nil
}

nested, err = seq.PeekNestedSeq()
if err != nil {
return nil, fmt.Errorf("DecodeTuple: nested peek failed at pos %d: %w", pos, err)
}
}

out := make([]any, 0, nested.ArgCount())
for i := 0; i < nested.ArgCount(); i++ {
valTyp, _, err := nested.PeekTypeWidth()

if err != nil {
return nil, fmt.Errorf("DecodeMapAny: nested value decode error at %d: %w", i+1, err)

return nil, fmt.Errorf("DecodeTuple: nested value peek error at %d: %w", i, err)
}
switch valTyp {
case types.TypeMap:
v, err := DecodeMapAny(nested) // delegate
var v any
if ordered {
v, err = DecodeOrderedMapAny(nested)
} else {
v, err = DecodeMapAny(nested)
}
if err != nil {
return nil, fmt.Errorf("DecodeMapAny: nested value decode error at %d: %w", i+1, err)
return nil, fmt.Errorf("DecodeTuple: nested map decode error at %d: %w", i, err)
}
out = append(out, v)

case types.TypeTuple:
v, err := DecodeTuple(nested) // delegate
v, err := DecodeTuple(nested)
if err != nil {
return nil, fmt.Errorf("DecodeMapAny: nested value decode error at %d: %w", i+1, err)
return nil, fmt.Errorf("DecodeTuple: nested tuple decode error at %d: %w", i, err)
}
out = append(out, v)

default:
valPayload, valTyp, err := nested.Next()
if err != nil {
return nil, fmt.Errorf("DecodeMapAny: nested value decode error at %d: %w", i+1, err)
return nil, fmt.Errorf("DecodeTuple: nested value next error at %d: %w", i, err)
}
v, err := DecodePrimitive(valTyp, valPayload)

if err != nil {
return nil, fmt.Errorf("DecodeMapAny: nested value decode error at %d: %w", i+1, err)
return nil, fmt.Errorf("DecodeTuple: primitive decode error at %d: %w", i, err)
}
out = append(out, v)
}

}

if !root {
if err := seq.Advance(); err != nil {
return nil, fmt.Errorf("DecodeTuple: advance failed at pos %d: %w", pos, err)
return nil, fmt.Errorf("DecodeTuple: advance failed: %w", err)
}
}
return out, nil
}

// Convenience wrappers
func DecodeTuple(seq *SeqGetAccess) ([]any, error) {
return DecodeTupleGeneric(seq, false)
return DecodeTupleGeneric(seq, false, false)
}

func DecodeTupleOrdered(seq *SeqGetAccess) ([]any, error) {
return DecodeTupleGeneric(seq, false, true)
}

// DecodeMapAny: decode a map[string]any from the current position in a SeqGetAccess.
Expand Down Expand Up @@ -208,15 +217,91 @@ func DecodeMapAny(seq *SeqGetAccess) (map[string]any, error) {
return out, nil
}

// DecodeOrderedMapAny decodes a map from the sequence into an OrderedMapAny,
// preserving insertion order of keys.
func DecodeOrderedMapAny(seq *SeqGetAccess) (*types.OrderedMapAny, error) {
pos := seq.CurrentIndex()
typ, width, err := seq.PeekTypeWidth()
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: peek failed at pos %d: %w", pos, err)
}
if typ != types.TypeMap {
return nil, fmt.Errorf("DecodeOrderedMapAny: type mismatch at pos %d — expected %v, got %v", pos, types.TypeMap, typ)
}
if width == 0 {
// nil/empty map
if err := seq.Advance(); err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: advance failed at pos %d: %w", pos, err)
}
return nil, nil
}

nested, err := seq.PeekNestedSeq()
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: nested peek failed at pos %d: %w", pos, err)
}

out := types.NewOrderedMapAny()
for i := 0; i < nested.ArgCount(); i += 2 {
// key
keyPayload, keyTyp, err := nested.Next()
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: key decode error at %d: %w", i, err)
}
if keyTyp != types.TypeString {
return nil, fmt.Errorf("DecodeOrderedMapAny: map key not string at %d, got %v", i, keyTyp)
}
key := string(keyPayload)

valTyp, _, err := nested.PeekTypeWidth()
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: nested value decode error at %d: %w", i+1, err)
}

switch valTyp {
case types.TypeMap:
v, err := DecodeOrderedMapAny(nested) // delegate recursively
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: nested value decode error at %d: %w", i+1, err)
}
out.Set(key, v)

case types.TypeTuple:
v, err := DecodeTuple(nested)
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: nested value decode error at %d: %w", i+1, err)
}
out.Set(key, v)

default:
valPayload, valTyp, err := nested.Next()
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: nested value decode error at %d: %w", i+1, err)
}
v, err := DecodePrimitive(valTyp, valPayload)
if err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: nested value decode error at %d: %w", i+1, err)
}
out.Set(key, v)
}
}

if err := seq.Advance(); err != nil {
return nil, fmt.Errorf("DecodeOrderedMapAny: advance failed at pos %d: %w", pos, err)
}
return out, nil
}

// Decode: convenience entry point for decoding a buffer that contains a top-level tuple.
// Decode decodes a buffer into Go values.
// Maps inside tuples are decoded as plain map[string]any.
func Decode(buf []byte) (any, error) {
seq, err := NewSeqGetAccess(buf)
if err != nil {
return nil, fmt.Errorf("Decode: failed to create sequence: %w", err)
}

// delegate to tuple decoder
vals, err := DecodeTupleGeneric(seq, true)
vals, err := DecodeTupleGeneric(seq, true, false) // ordered=false
if err != nil {
return nil, fmt.Errorf("Decode: tuple decode failed: %w", err)
}
Expand All @@ -225,3 +310,21 @@ func Decode(buf []byte) (any, error) {
}
return vals, nil
}

// DecodeOrdered decodes a buffer into Go values.
// Maps inside tuples are decoded as *types.OrderedMapAny.
func DecodeOrdered(buf []byte) (any, error) {
seq, err := NewSeqGetAccess(buf)
if err != nil {
return nil, fmt.Errorf("DecodeOrdered: failed to create sequence: %w", err)
}

vals, err := DecodeTupleGeneric(seq, true, true) // ordered=true
if err != nil {
return nil, fmt.Errorf("DecodeOrdered: tuple decode failed: %w", err)
}
if len(vals) == 1 {
return vals[0], nil
}
return vals, nil
}
28 changes: 28 additions & 0 deletions access/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,34 @@ func (g *GetAccess) GetMapAny(pos int) (map[string]any, error) {
return out, nil
}

// GetMapOrderedAny decodes a map at the given position into an OrderedMapAny,
// preserving insertion order of keys.
func (g *GetAccess) GetMapOrderedAny(pos int) (*types.OrderedMapAny, error) {
tp, start, end := g.rangeAt(pos)
if end < start || tp != types.TypeMap {
return nil, errors.New("decode error")
}
if end == start {
return nil, nil // nil map
}

nested := NewGetAccess(g.buf[start:end])
out := types.NewOrderedMapAny()

for i := 0; i < nested.argCount; i += 2 {
key, err := nested.GetString(i)
if err != nil {
return nil, fmt.Errorf("ordered map key decode error at %d: %w", i, err)
}
val, err := GetAny(nested, i+1)
if err != nil {
return nil, fmt.Errorf("ordered map value decode error at %d: %w", i+1, err)
}
out.Set(key, val)
}
return out, nil
}

func (g *GetAccess) GetMapStr(pos int) (map[string]string, error) {
tp, start, end := g.rangeAt(pos)
if end < start || tp != types.TypeMap {
Expand Down
34 changes: 34 additions & 0 deletions access/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package access
import (
"testing"

"github.com/quickwritereader/PackOS/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -61,6 +62,39 @@ func TestGetAccess_Map2(t *testing.T) {
}, m)
}

func TestGetAccess_MapOrderedAny(t *testing.T) {
buf := []byte{
0x27, 0x00, 0xE0, 0x00,
0x56, 0x00, 0x26, 0x00, 0x4E, 0x00, 0x6E, 0x00, 0x90, 0x00,
'r', 'o', 'l', 'e',
'a', 'd', 'm', 'i', 'n',
'u', 's', 'e', 'r',
'a', 'l', 'i', 'c', 'e',
}
get := NewGetAccess(buf)

om, err := get.GetMapOrderedAny(0)
require.NoError(t, err)
require.NotNil(t, om)

// Build expected ordered map
expected := types.NewOrderedMapAny(
types.OPAny("role", "admin"),
types.OPAny("user", "alice"),
)

// Use Equal method to compare
assert.True(t, om.Equal(expected), "decoded OrderedMapAny does not match expected")

// Also check insertion order explicitly
keys := []string{}
for k := range om.KeysIter() {
keys = append(keys, k)
}

assert.Equal(t, []string{"role", "user"}, keys)
}

func TestGetAccess_IntThenMapWithInnerMapAndString(t *testing.T) {
buf := []byte{
0x31, 0x00, 0x17, 0x00, 0xB0, 0x01,
Expand Down
27 changes: 27 additions & 0 deletions access/put.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ func packAnyValue(p *PutAccess, v any, useNumeric bool) error {
p.AddMap(val)
case []string:
p.AddStringArray(val)
case *types.OrderedMap[any]:
err = p.AddMapAnyOrdered(val, useNumeric)
case []interface{}:
err = p.AddAnyTuple(val, useNumeric)
case Packable:
Expand Down Expand Up @@ -525,6 +527,8 @@ func packAnyValueSortedMap(p *PutAccess, v any, useNumeric bool) error {
p.AddMapAny(val, useNumeric)
case map[string][]byte:
p.AddMapSortedKey(val)
case *types.OrderedMap[any]:
err = p.AddMapAnyOrdered(val, useNumeric)
case Packable:
val.PackInto(p)
case []interface{}:
Expand Down Expand Up @@ -578,6 +582,29 @@ func (p *PutAccess) AddMapAnySortedKey(m map[string]any, useNumeric bool) error
return nil
}

// AddMapAnyOrdered encodes an OrderedMap[string→any] preserving insertion order.
func (p *PutAccess) AddMapAnyOrdered(om *types.OrderedMap[any], useNumeric bool) error {
// Write map header
p.offsets = binary.LittleEndian.AppendUint16(
p.offsets,
types.EncodeHeader(p.position, types.TypeMap),
)

if om != nil && om.Len() > 0 {
nested := NewPutAccessFromPool()
for k, v := range om.ItemsIter() {
// Add key
nested.AddString(k)
// Add value
if err := packAnyValue(nested, v, useNumeric); err != nil {
return fmt.Errorf("AddMapAnyOrdered: key %q: %w", k, err)
}
}
p.appendAndReleaseNested(nested)
}
return nil
}

func (p *PutAccess) appendAndReleaseNested(nested *PutAccess) {

p.buf = nested.PackAppend(p.buf)
Expand Down
Loading