blob: a8673109c8f6972b8587ba809803a31beb89795d [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package event
import (
"fmt"
"math"
"reflect"
"strconv"
"unsafe"
)
// Value holds any value in an efficient way that avoids allocations for
// most types.
type Value struct {
packed uint64
untyped interface{}
}
// Label is a named value.
type Label struct {
Name string
Value Value
}
// Equal reports whether two labels are equal.
func (l1 Label) Equal(l2 Label) bool {
return l1.Name == l2.Name && l1.Value.Equal(l2.Value)
}
// stringptr is used in untyped when the Value is a string
type stringptr unsafe.Pointer
// bytesptr is used in untyped when the Value is a byte slice
type bytesptr unsafe.Pointer
// int64Kind is used in untyped when the Value is a signed integer
type int64Kind struct{}
// uint64Kind is used in untyped when the Value is an unsigned integer
type uint64Kind struct{}
// float64Kind is used in untyped when the Value is a floating point number
type float64Kind struct{}
// boolKind is used in untyped when the Value is a boolean
type boolKind struct{}
// HasValue returns true if the value is set to any type.
func (v *Value) HasValue() bool { return v.untyped != nil }
// Equal reports whether two values are equal.
func (v1 *Value) Equal(v2 Value) bool {
if !v1.HasValue() {
return !v2.HasValue()
}
if !v2.HasValue() {
return false
}
switch {
case v1.IsString():
return v2.IsString() && v1.String() == v2.String()
case v1.IsInt64():
return v2.IsInt64() && v1.packed == v2.packed
case v1.IsUint64():
return v2.IsUint64() && v1.packed == v2.packed
case v1.IsFloat64():
return v2.IsFloat64() && v1.Float64() == v2.Float64()
case v1.IsBool():
return v2.IsBool() && v1.packed == v2.packed
default:
return v1.untyped == v2.untyped
}
}
// ValueOf returns a Value for the supplied value.
func ValueOf(value interface{}) Value {
return Value{untyped: value}
}
// Interface returns the value.
// This will never panic, things that were not set using SetInterface will be
// unpacked and returned anyway.
func (v Value) Interface() interface{} {
switch {
case v.IsString():
return v.String()
case v.IsInt64():
return v.Int64()
case v.IsUint64():
return v.Uint64()
case v.IsFloat64():
return v.Float64()
case v.IsBool():
return v.Bool()
default:
return v.untyped
}
}
// StringOf returns a new Value for a string.
func StringOf(s string) Value {
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
return Value{packed: uint64(hdr.Len), untyped: stringptr(hdr.Data)}
}
// String returns the value as a string.
// This does not panic if v's Kind is not String, instead, it returns a string
// representation of the value in all cases.
func (v Value) String() string {
if sp, ok := v.untyped.(stringptr); ok {
var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
hdr.Data = uintptr(sp)
hdr.Len = int(v.packed)
return s
}
// not a string, convert to one
switch {
case v.IsInt64():
return strconv.FormatInt(v.Int64(), 10)
case v.IsUint64():
return strconv.FormatUint(v.Uint64(), 10)
case v.IsFloat64():
return strconv.FormatFloat(v.Float64(), 'g', -1, 64)
case v.IsBool():
if v.Bool() {
return "true"
} else {
return "false"
}
default:
return fmt.Sprint(v.Interface())
}
}
// IsString returns true if the value was built with StringOf.
func (v Value) IsString() bool {
_, ok := v.untyped.(stringptr)
return ok
}
// BytesOf returns a new Value for a string.
func BytesOf(data []byte) Value {
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data))
return Value{packed: uint64(hdr.Len), untyped: bytesptr(hdr.Data)}
}
// Bytes returns the value as a bytes array.
func (v Value) Bytes() []byte {
bp, ok := v.untyped.(bytesptr)
if !ok {
panic("Bytes called on non []byte value")
}
var buf []byte
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
hdr.Data = uintptr(bp)
hdr.Len = int(v.packed)
hdr.Cap = hdr.Len // TODO: is this safe?
return buf
}
// IsBytes returns true if the value was built with BytesOf.
func (v Value) IsBytes() bool {
_, ok := v.untyped.(bytesptr)
return ok
}
// Int64Of returns a new Value for a signed integer.
func Int64Of(u int64) Value {
return Value{packed: uint64(u), untyped: int64Kind{}}
}
// Int64 returns the int64 from a value that was set with SetInt64.
// It will panic for any value for which IsInt64 is not true.
func (v Value) Int64() int64 {
if !v.IsInt64() {
panic("Int64 called on non int64 value")
}
return int64(v.packed)
}
// IsInt64 returns true if the value was built with SetInt64.
func (v Value) IsInt64() bool {
_, ok := v.untyped.(int64Kind)
return ok
}
// Uint64Of returns a new Value for an unsigned integer.
func Uint64Of(u uint64) Value {
return Value{packed: u, untyped: uint64Kind{}}
}
// Uint64 returns the uint64 from a value that was set with SetUint64.
// It will panic for any value for which IsUint64 is not true.
func (v Value) Uint64() uint64 {
if !v.IsUint64() {
panic("Uint64 called on non uint64 value")
}
return v.packed
}
// IsUint64 returns true if the value was built with SetUint64.
func (v Value) IsUint64() bool {
_, ok := v.untyped.(uint64Kind)
return ok
}
// Float64Of returns a new Value for a floating point number.
func Float64Of(f float64) Value {
return Value{packed: math.Float64bits(f), untyped: float64Kind{}}
}
// Float64 returns the float64 from a value that was set with SetFloat64.
// It will panic for any value for which IsFloat64 is not true.
func (v Value) Float64() float64 {
if !v.IsFloat64() {
panic("Float64 called on non float64 value")
}
return math.Float64frombits(v.packed)
}
// IsFloat64 returns true if the value was built with SetFloat64.
func (v Value) IsFloat64() bool {
_, ok := v.untyped.(float64Kind)
return ok
}
// BoolOf returns a new Value for a bool.
func BoolOf(b bool) Value {
if b {
return Value{packed: 1, untyped: boolKind{}}
}
return Value{packed: 0, untyped: boolKind{}}
}
// Bool returns the bool from a value that was set with SetBool.
// It will panic for any value for which IsBool is not true.
func (v Value) Bool() bool {
if !v.IsBool() {
panic("Bool called on non bool value")
}
if v.packed != 0 {
return true
}
return false
}
// IsBool returns true if the value was built with SetBool.
func (v Value) IsBool() bool {
_, ok := v.untyped.(boolKind)
return ok
}