Skip to content

Commit

Permalink
Handle array of values
Browse files Browse the repository at this point in the history
  • Loading branch information
pellared committed Jan 19, 2024
1 parent 312f234 commit 688d9aa
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 10 deletions.
51 changes: 43 additions & 8 deletions log/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ import (
type Value struct {
_ [0]func() // disallow ==
// num holds the value for Kinds: Int64, Float64, and Bool,
// the length for String, Bytes, Group.
// the length for String, Bytes, List, Group.
num uint64
// If any is of type Kind, then the value is in num as described above.
// Otherwise (if is of type stringptr, bytesptr or groupptr) it contains the value.
// Otherwise (if is of type stringptr, listptr, sliceptr or groupptr) it contains the value.
any any
}

type (
stringptr *byte // used in Value.any when the Value is a string
bytesptr *byte // used in Value.any when the Value is a []byte
listptr *Value // used in Value.any when the Value is a []Value
groupptr *KeyValue // used in Value.any when the Value is a []KeyValue
)

Expand All @@ -44,6 +45,7 @@ const (
KindInt64
KindString
KindBytes
KindList
KindGroup
)

Expand All @@ -54,6 +56,7 @@ var kindStrings = []string{
"Int64",
"String",
"Bytes",
"List",
"Group",
}

Expand All @@ -75,6 +78,8 @@ func (v Value) Kind() Kind {
return KindString
case bytesptr:
return KindBytes
case listptr:
return KindList
case groupptr:
return KindGroup
default:
Expand Down Expand Up @@ -117,22 +122,28 @@ func BytesValue(v []byte) Value {
return Value{num: uint64(len(v)), any: bytesptr(unsafe.SliceData(v))}
}

// ListValue returns a [Value] for a list of [Value].
// The caller must not subsequently mutate the argument slice.
func ListValue(vs ...Value) Value {
return Value{num: uint64(len(vs)), any: listptr(unsafe.SliceData(vs))}
}

// GroupValue returns a new [Value] for a list of key-value pairs.
// The caller must not subsequently mutate the argument slice.
func GroupValue(as ...KeyValue) Value {
func GroupValue(kvs ...KeyValue) Value {
// Remove empty groups.
// It is simpler overall to do this at construction than
// to check each Group recursively for emptiness.
if n := countEmptyGroups(as); n > 0 {
as2 := make([]KeyValue, 0, len(as)-n)
for _, a := range as {
if n := countEmptyGroups(kvs); n > 0 {
as2 := make([]KeyValue, 0, len(kvs)-n)
for _, a := range kvs {
if !a.Value.isEmptyGroup() {
as2 = append(as2, a)
}
}
as = as2
kvs = as2
}
return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
return Value{num: uint64(len(kvs)), any: groupptr(unsafe.SliceData(kvs))}
}

// countEmptyGroups returns the number of empty group values in its argument.
Expand All @@ -151,6 +162,8 @@ func (v Value) Any() any {
switch v.Kind() {
case KindGroup:
return v.group()
case KindList:
return v.list()
case KindInt64:
return int64(v.num)
case KindFloat64:
Expand Down Expand Up @@ -232,6 +245,19 @@ func (v Value) bytes() []byte {
return unsafe.Slice((*byte)(v.any.(bytesptr)), v.num)
}

// List returns v's value as a []Value.
// It panics if v's [Kind] is not [KindList].
func (v Value) List() []Value {
if sp, ok := v.any.(listptr); ok {
return unsafe.Slice((*Value)(sp), v.num)
}
panic("List: bad kind")
}

func (v Value) list() []Value {
return unsafe.Slice((*Value)(v.any.(listptr)), v.num)
}

// Group returns v's value as a []KeyValue.
// It panics if v's [Kind] is not [KindGroup].
func (v Value) Group() []KeyValue {
Expand Down Expand Up @@ -264,6 +290,8 @@ func (v Value) Equal(w Value) bool {
return v.str() == w.str()
case KindFloat64:
return v.float() == w.float()
case KindList:
return sliceEqualFunc(v.list(), w.list(), Value.Equal)
case KindGroup:
return sliceEqualFunc(v.group(), w.group(), KeyValue.Equal)
case KindBytes:
Expand Down Expand Up @@ -302,6 +330,8 @@ func (v Value) append(dst []byte) []byte {
return fmt.Append(dst, v.bytes())
case KindGroup:
return fmt.Append(dst, v.group())
case KindList:
return fmt.Append(dst, v.list())
case KindEmpty:
return append(dst, emptyString...)
default:
Expand Down Expand Up @@ -346,6 +376,11 @@ func Bytes(key string, v []byte) KeyValue {
return KeyValue{key, BytesValue(v)}
}

// Bytes returns an KeyValue for a list of [Value].
func List(key string, args ...Value) KeyValue {
return KeyValue{key, ListValue(args...)}
}

// Group returns an KeyValue for a Group [Value].
//
// Use Group to collect several key-value pairs under a single
Expand Down
20 changes: 18 additions & 2 deletions log/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ func TestValueEqual(t *testing.T) {
BoolValue(false),
StringValue("hi"),
BytesValue([]byte{1, 3, 5}),
GroupValue(Bool("b", true), Int("i", 3), Bytes("b", []byte{3, 5, 7})),
ListValue(IntValue(3), StringValue("foo")),
GroupValue(Bool("b", true), Int("i", 3)),
GroupValue(List("l", IntValue(3), StringValue("foo")), Bytes("b", []byte{3, 5, 7})),
}
for i, v1 := range vals {
for j, v2 := range vals {
Expand All @@ -54,6 +56,7 @@ func TestValueString(t *testing.T) {
{BoolValue(true), "true"},
{StringValue("foo"), "foo"},
{BytesValue([]byte{2, 4, 6}), "[2 4 6]"},
{ListValue(IntValue(3), StringValue("foo")), "[3 foo]"},
{GroupValue(Int("a", 1), Bool("b", true)), "[a=1 b=true]"},
{Value{}, "<nil>"},
} {
Expand Down Expand Up @@ -123,6 +126,7 @@ func TestValueAny(t *testing.T) {
{int64(11), Int64Value(11)},
{1.5, Float64Value(1.5)},
{[]byte{1, 2, 3}, BytesValue([]byte{1, 2, 3})},
{[]Value{IntValue(3)}, ListValue(IntValue(3))},
{[]KeyValue{Int("i", 3)}, GroupValue(Int("i", 3))},
{nil, Value{}},
} {
Expand All @@ -131,7 +135,7 @@ func TestValueAny(t *testing.T) {
}
}

func TestEmptyGroup(t *testing.T) {
func TestGroupValueWithEmptyGroups(t *testing.T) {
g := GroupValue(
Int("a", 1),
Group("g1", Group("g2")),
Expand All @@ -141,6 +145,18 @@ func TestEmptyGroup(t *testing.T) {
assert.Equal(t, want, got)
}

func TestEmptyGroup(t *testing.T) {
g := Group("g")
got := g.Value.Group()
assert.Nil(t, got)
}

func TestEmptyList(t *testing.T) {
l := ListValue()
got := l.List()
assert.Nil(t, got)
}

// A Value with "unsafe" strings is significantly faster:
// safe: 1785 ns/op, 0 allocs
// unsafe: 690 ns/op, 0 allocs
Expand Down

0 comments on commit 688d9aa

Please sign in to comment.