aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArne Juul <arnej@yahooinc.com>2023-05-24 17:04:46 +0000
committerArne Juul <arnej@yahooinc.com>2023-05-24 18:38:35 +0000
commit801b46f85d04fbfee38b88312419393e20340571 (patch)
treed94e4daba396d0cc57fbfabb51a06821042f34c5
parent1d445d46f6c4662d64ff77c81b068163d4077479 (diff)
add minimal port of Slime APIarnej/golang-slime-port-1
-rw-r--r--client/go/internal/slime/array_traverser.go8
-rw-r--r--client/go/internal/slime/array_value.go96
-rw-r--r--client/go/internal/slime/binary_decoder.go188
-rw-r--r--client/go/internal/slime/binary_encoder.go191
-rw-r--r--client/go/internal/slime/binary_format.go59
-rw-r--r--client/go/internal/slime/binary_format_test.go378
-rw-r--r--client/go/internal/slime/bool_value.go25
-rw-r--r--client/go/internal/slime/cursor.go28
-rw-r--r--client/go/internal/slime/data_value.go25
-rw-r--r--client/go/internal/slime/double_value.go29
-rw-r--r--client/go/internal/slime/inspector.go20
-rw-r--r--client/go/internal/slime/json_format.go141
-rw-r--r--client/go/internal/slime/json_format_test.go34
-rw-r--r--client/go/internal/slime/long_value.go29
-rw-r--r--client/go/internal/slime/nix_value.go22
-rw-r--r--client/go/internal/slime/object_traverser.go8
-rw-r--r--client/go/internal/slime/object_value.go94
-rw-r--r--client/go/internal/slime/slime.go63
-rw-r--r--client/go/internal/slime/string_value.go29
-rw-r--r--client/go/internal/slime/type.go17
-rw-r--r--client/go/internal/slime/value_base.go37
21 files changed, 1521 insertions, 0 deletions
diff --git a/client/go/internal/slime/array_traverser.go b/client/go/internal/slime/array_traverser.go
new file mode 100644
index 00000000000..f0154bc064e
--- /dev/null
+++ b/client/go/internal/slime/array_traverser.go
@@ -0,0 +1,8 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type ArrayTraverser interface {
+ Entry(idx int, value Inspector)
+}
diff --git a/client/go/internal/slime/array_value.go b/client/go/internal/slime/array_value.go
new file mode 100644
index 00000000000..e5f84f47442
--- /dev/null
+++ b/client/go/internal/slime/array_value.go
@@ -0,0 +1,96 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type ArrayValue struct {
+ valueBase
+ v []Cursor
+}
+
+func newArrayValue() *ArrayValue {
+ return &ArrayValue{
+ v: make([]Cursor, 0, 10),
+ }
+}
+
+func (a *ArrayValue) addElement(elem Cursor) Cursor {
+ a.v = append(a.v, elem)
+ return elem
+}
+
+func (a *ArrayValue) Valid() bool {
+ return true
+}
+
+func (a *ArrayValue) Type() Type {
+ return ARRAY
+}
+
+func (a *ArrayValue) Entries() int {
+ return len(a.v)
+}
+
+func (a *ArrayValue) TraverseArray(traverser ArrayTraverser) {
+ for idx, value := range a.v {
+ traverser.Entry(idx, value)
+ }
+}
+
+func (a *ArrayValue) Entry(idx int) Inspector {
+ if idx < len(a.v) {
+ return a.v[idx]
+ }
+ return InvalidNix
+}
+
+func (a *ArrayValue) MutableEntry(idx int) Cursor {
+ if idx < len(a.v) {
+ return a.v[idx]
+ }
+ return InvalidNix
+}
+
+func (a *ArrayValue) AddNix() Inspector {
+ return a.addElement(ValidNix)
+}
+
+func (a *ArrayValue) AddBool(value bool) Inspector {
+ return a.addElement(newBoolValue(value))
+}
+
+func (a *ArrayValue) AddLong(value int64) Inspector {
+ n := newLongValue(value)
+ a.v = append(a.v, n)
+ return n
+}
+
+func (a *ArrayValue) AddDouble(value float64) Inspector {
+ n := newDoubleValue(value)
+ a.v = append(a.v, n)
+ return n
+}
+
+func (a *ArrayValue) AddString(value string) Inspector {
+ n := newStringValue(value)
+ a.v = append(a.v, n)
+ return n
+}
+
+func (a *ArrayValue) AddData(value []byte) Inspector {
+ n := newDataValue(value)
+ a.v = append(a.v, n)
+ return n
+}
+
+func (a *ArrayValue) AddArray() Cursor {
+ n := newArrayValue()
+ a.v = append(a.v, n)
+ return n
+}
+
+func (a *ArrayValue) AddObject() Cursor {
+ n := newObjectValue()
+ a.v = append(a.v, n)
+ return n
+}
diff --git a/client/go/internal/slime/binary_decoder.go b/client/go/internal/slime/binary_decoder.go
new file mode 100644
index 00000000000..b8e9340a8cb
--- /dev/null
+++ b/client/go/internal/slime/binary_decoder.go
@@ -0,0 +1,188 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type binaryDecoder struct {
+ buf []byte
+ pos int
+ symbols []string
+ failed bool
+ failure string
+}
+
+var emptyByteSlice = make([]byte, 0)
+
+func newBinaryDecoder(input []byte) binaryDecoder {
+ return binaryDecoder{
+ buf: input,
+ pos: 0,
+ failed: false,
+ }
+}
+
+func (in *binaryDecoder) fail(reason string) {
+ in.failed = true
+ in.failure = reason
+}
+
+func (in *binaryDecoder) getByte() byte {
+ var b byte = 0
+ if !in.failed {
+ if in.pos < len(in.buf) {
+ b = in.buf[in.pos]
+ in.pos++
+ } else {
+ in.fail("buffer underflow")
+ }
+ }
+ return b
+}
+
+func (in *binaryDecoder) getBytes(sz int) []byte {
+ if !in.failed {
+ if in.pos+sz <= len(in.buf) {
+ start := in.pos
+ endp := start + sz
+ in.pos = endp
+ return in.buf[start:endp]
+ }
+ in.fail("buffer underflow")
+ }
+ return emptyByteSlice
+}
+
+func (in *binaryDecoder) read_bytes_le(sz uint32) uint64 {
+ var value uint64 = 0
+ shift := 0
+ for i := uint32(0); i < sz; i++ {
+ b := uint64(in.getByte())
+ value = value | (b << shift)
+ shift = shift + 8
+ }
+ return value
+}
+
+func (in *binaryDecoder) read_bytes_be(sz uint32) uint64 {
+ var value uint64 = 0
+ shift := 56
+ for i := uint32(0); i < sz; i++ {
+ b := uint64(in.getByte())
+ value = value | (b << shift)
+ shift = shift - 8
+ }
+ return value
+}
+
+func (in *binaryDecoder) read_cmpr_int() int {
+ next := in.getByte()
+ var value int64 = int64(next & 0x7f)
+ shift := 0
+ for (next & 0x80) != 0 {
+ shift += 7
+ next = in.getByte()
+ value = value | ((int64(next) & 0x7f) << shift)
+ if shift > 32 || value > 0x7fff_ffff {
+ in.fail("compressed int overflow")
+ value = 0
+ next = 0
+ }
+ }
+ return int(value)
+}
+
+func (in *binaryDecoder) read_size(meta uint32) int {
+ if meta != 0 {
+ return int(meta - 1)
+ }
+ return in.read_cmpr_int()
+}
+
+func (in *binaryDecoder) decodeSymbolTable() {
+ numSymbols := in.read_cmpr_int()
+ in.symbols = make([]string, numSymbols)
+ for i := 0; i < numSymbols; i++ {
+ sz := in.read_cmpr_int()
+ buf := in.getBytes(sz)
+ in.symbols[i] = string(buf)
+ }
+}
+
+func (in *binaryDecoder) decodeValue() Cursor {
+ b := in.getByte()
+ ty := decode_type(b)
+ meta := decode_meta(b)
+ switch ty {
+ case NIX:
+ return ValidNix
+ case BOOL:
+ return in.decodeBOOL(meta)
+ case LONG:
+ return in.decodeLONG(meta)
+ case DOUBLE:
+ return in.decodeDOUBLE(meta)
+ case STRING:
+ return in.decodeSTRING(meta)
+ case DATA:
+ return in.decodeDATA(meta)
+ case ARRAY:
+ return in.decodeARRAY(meta)
+ case OBJECT:
+ return in.decodeOBJECT(meta)
+ }
+ panic("bad type")
+ // return InvalidNix
+}
+
+func (in *binaryDecoder) decodeBOOL(meta uint32) Cursor {
+ return newBoolValue(meta != 0)
+}
+
+func (in *binaryDecoder) decodeLONG(meta uint32) Cursor {
+ var encoded uint64 = in.read_bytes_le(meta)
+ return newLongValue(decode_zigzag(encoded))
+}
+
+func (in *binaryDecoder) decodeDOUBLE(meta uint32) Cursor {
+ var encoded uint64 = in.read_bytes_be(meta)
+ return newDoubleValue(decode_double(encoded))
+}
+
+func (in *binaryDecoder) decodeSTRING(meta uint32) Cursor {
+ sz := in.read_size(meta)
+ image := in.getBytes(sz)
+ return newStringValue(string(image))
+}
+
+func (in *binaryDecoder) decodeDATA(meta uint32) Cursor {
+ sz := in.read_size(meta)
+ image := in.getBytes(sz)
+ return newDataValue(image)
+}
+
+func (in *binaryDecoder) decodeARRAY(meta uint32) Cursor {
+ res := newArrayValue()
+ sz := in.read_size(meta)
+ for i := 0; i < sz; i++ {
+ elem := in.decodeValue()
+ res.addElement(elem)
+ }
+ return res
+}
+
+func (in *binaryDecoder) decodeOBJECT(meta uint32) Cursor {
+ res := newObjectValue()
+ sz := in.read_size(meta)
+ for i := 0; i < sz; i++ {
+ symbol := in.read_cmpr_int()
+ if symbol < len(in.symbols) {
+ name := in.symbols[symbol]
+ elem := in.decodeValue()
+ res.addElement(name, elem)
+ } else {
+ in.fail("symbol id out of range decoding object")
+ return res
+ }
+ }
+ return res
+}
diff --git a/client/go/internal/slime/binary_encoder.go b/client/go/internal/slime/binary_encoder.go
new file mode 100644
index 00000000000..01b2221df1b
--- /dev/null
+++ b/client/go/internal/slime/binary_encoder.go
@@ -0,0 +1,191 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type binaryEncoder struct {
+ buf []byte
+ symbols map[string]uint32
+}
+
+func newBinaryEncoder() binaryEncoder {
+ return binaryEncoder{
+ buf: make([]byte, 0, 128),
+ symbols: make(map[string]uint32),
+ }
+}
+
+func (out *binaryEncoder) pos() int {
+ return len(out.buf)
+}
+
+func (out *binaryEncoder) put(b byte) {
+ out.buf = append(out.buf, b)
+}
+
+func (out *binaryEncoder) putAt(pos int, b byte) {
+ out.buf[pos] = b
+}
+
+func (out *binaryEncoder) putBytes(bytes []byte) {
+ out.buf = append(out.buf, bytes...)
+}
+
+func (out *binaryEncoder) putString(value string) {
+ out.buf = append(out.buf, value...)
+}
+
+func (out *binaryEncoder) encode_cmpr_uint(value uint32) {
+ var next byte = byte(value & 0x7f)
+ value = value >> 7
+ for value != 0 {
+ next = next | 0x80
+ out.put(next)
+ next = byte(value & 0x7f)
+ value = value >> 7
+ }
+ out.put(next)
+}
+
+func (out *binaryEncoder) write_type_and_size(ty Type, size uint32) {
+ if size <= 30 {
+ out.put(encode_type_and_meta(ty, size+1))
+ } else {
+ out.put(encode_type_and_meta(ty, 0))
+ out.encode_cmpr_uint(size)
+ }
+}
+
+func (out *binaryEncoder) write_type_and_bytes_le(ty Type, bits uint64) {
+ pos := out.pos()
+ var val byte = 0
+ out.put(val)
+ var cnt uint32 = 0
+ for bits != 0 {
+ val = byte(bits & 0xff)
+ bits = bits >> 8
+ out.put(val)
+ cnt++
+ }
+ val = encode_type_and_meta(ty, cnt)
+ out.putAt(pos, val)
+}
+
+func (out *binaryEncoder) write_type_and_bytes_be(ty Type, bits uint64) {
+ pos := out.pos()
+ var val byte = 0
+ out.put(val)
+ var cnt uint32 = 0
+ for bits != 0 {
+ val = byte(bits >> 56)
+ bits = bits << 8
+ out.put(val)
+ cnt++
+ }
+ val = encode_type_and_meta(ty, cnt)
+ out.putAt(pos, val)
+}
+
+func (out *binaryEncoder) encodeNIX() {
+ out.put(byte(NIX))
+}
+
+func (out *binaryEncoder) encodeValue(inspector Inspector) {
+ switch inspector.Type() {
+ case NIX:
+ out.encodeNIX()
+ case BOOL:
+ out.encodeBOOL(inspector.AsBool())
+ case LONG:
+ out.encodeLONG(inspector.AsLong())
+ case DOUBLE:
+ out.encodeDOUBLE(inspector.AsDouble())
+ case STRING:
+ out.encodeSTRING(inspector.AsString())
+ case DATA:
+ out.encodeDATA(inspector.AsData())
+ case ARRAY:
+ out.encodeARRAY(inspector)
+ case OBJECT:
+ out.encodeOBJECT(inspector)
+ default:
+ panic("Should not be reached")
+ }
+}
+
+func (out *binaryEncoder) encodeBOOL(value bool) {
+ var meta uint32
+ if value {
+ meta = 1
+ } else {
+ meta = 0
+ }
+ out.put(encode_type_and_meta(BOOL, meta))
+}
+
+func (out *binaryEncoder) encodeLONG(value int64) {
+ out.write_type_and_bytes_le(LONG, encode_zigzag(value))
+}
+
+func (out *binaryEncoder) encodeDOUBLE(value float64) {
+ out.write_type_and_bytes_be(DOUBLE, encode_double(value))
+}
+
+func (out *binaryEncoder) encodeSTRING(value string) {
+ out.write_type_and_size(STRING, uint32(len(value)))
+ out.putString(value)
+}
+
+func (out *binaryEncoder) encodeDATA(value []byte) {
+ out.write_type_and_size(DATA, uint32(len(value)))
+ out.putBytes(value)
+}
+
+func (out *binaryEncoder) Entry(idx int, value Inspector) {
+ out.encodeValue(value)
+}
+
+func (out *binaryEncoder) encodeARRAY(inspector Inspector) {
+ out.write_type_and_size(ARRAY, uint32(inspector.Entries()))
+ inspector.TraverseArray(out)
+}
+
+func (out *binaryEncoder) findSymbolId(name string) uint32 {
+ if value, ok := out.symbols[name]; ok {
+ return value
+ }
+ value := uint32(len(out.symbols))
+ out.symbols[name] = value
+ return value
+}
+
+func (out *binaryEncoder) Field(name string, value Inspector) {
+ symbol := out.findSymbolId(name)
+ out.encode_cmpr_uint(symbol)
+ out.encodeValue(value)
+}
+
+func (out *binaryEncoder) encodeOBJECT(inspector Inspector) {
+ out.write_type_and_size(OBJECT, uint32(inspector.Fields()))
+ inspector.TraverseObject(out)
+}
+
+func (out *binaryEncoder) prependSymbolTable() []byte {
+ numSymbols := len(out.symbols)
+ sorted := make([]string, numSymbols)
+ for name, id := range out.symbols {
+ sorted[id] = name
+ }
+ tmp := newBinaryEncoder()
+ tmp.encode_cmpr_uint(uint32(numSymbols))
+ for _, name := range sorted {
+ sz := uint32(len(name))
+ tmp.encode_cmpr_uint(sz)
+ tmp.putString(name)
+ }
+ res := make([]byte, len(out.buf)+len(tmp.buf))
+ split := len(tmp.buf)
+ copy(res[:split], tmp.buf)
+ copy(res[split:], out.buf)
+ return res
+}
diff --git a/client/go/internal/slime/binary_format.go b/client/go/internal/slime/binary_format.go
new file mode 100644
index 00000000000..4be9cc5d4e8
--- /dev/null
+++ b/client/go/internal/slime/binary_format.go
@@ -0,0 +1,59 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+import (
+ "math"
+)
+
+func BinaryEncode(s Slime) []byte {
+ codec := newBinaryEncoder()
+ codec.encodeValue(s.Get())
+ return codec.prependSymbolTable()
+}
+
+func BinaryDecode(input []byte) Slime {
+ codec := newBinaryDecoder(input)
+ codec.decodeSymbolTable()
+ root := codec.decodeValue()
+ s := Slime{root}
+ if codec.failed {
+ s.Wrap("partial_result")
+ s.Get().SetString("error_message", codec.failure)
+ s.Get().SetLong("buffer_position", int64(codec.pos))
+ }
+ return s
+}
+
+func encode_zigzag(x int64) uint64 {
+ bot63 := x << 1
+ inv := x >> 63 // note: ASR because x is signed
+ return uint64(bot63 ^ inv)
+}
+
+func decode_zigzag(x uint64) int64 {
+ top63 := int64(x >> 1) // note: LSR because x is unsigned
+ inv := -int64(x & 1)
+ return top63 ^ inv
+}
+
+func encode_double(x float64) uint64 {
+ return math.Float64bits(x)
+}
+
+func decode_double(x uint64) float64 {
+ return math.Float64frombits(x)
+}
+
+func encode_type_and_meta(t Type, meta uint32) byte {
+ return byte((meta << 3) | (uint32(t) & 0x7))
+}
+
+func decode_type(type_and_meta byte) Type {
+ return Type(type_and_meta & 0x7)
+}
+
+func decode_meta(type_and_meta byte) uint32 {
+ return uint32(type_and_meta >> 3)
+}
diff --git a/client/go/internal/slime/binary_format_test.go b/client/go/internal/slime/binary_format_test.go
new file mode 100644
index 00000000000..5b0989ad0f1
--- /dev/null
+++ b/client/go/internal/slime/binary_format_test.go
@@ -0,0 +1,378 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package slime
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func u64(i int64) uint64 {
+ return uint64(i)
+}
+
+func i64(i int64) int64 {
+ return i
+}
+
+func TestZigZag(t *testing.T) {
+ assert.Equal(t, u64(0), encode_zigzag(0))
+ assert.Equal(t, i64(0), decode_zigzag(encode_zigzag(0)))
+
+ assert.Equal(t, u64(1), encode_zigzag(-1))
+ assert.Equal(t, i64(-1), decode_zigzag(encode_zigzag(-1)))
+
+ assert.Equal(t, u64(2), encode_zigzag(1))
+ assert.Equal(t, i64(1), decode_zigzag(encode_zigzag(1)))
+
+ assert.Equal(t, u64(3), encode_zigzag(-2))
+ assert.Equal(t, i64(-2), decode_zigzag(encode_zigzag(-2)))
+
+ assert.Equal(t, u64(4), encode_zigzag(2))
+ assert.Equal(t, i64(2), decode_zigzag(encode_zigzag(2)))
+
+ assert.Equal(t, u64(1999), encode_zigzag(-1000))
+ assert.Equal(t, i64(-1000), decode_zigzag(encode_zigzag(-1000)))
+
+ assert.Equal(t, u64(2000), encode_zigzag(1000))
+ assert.Equal(t, i64(1000), decode_zigzag(encode_zigzag(1000)))
+
+ assert.Equal(t, u64(-1), encode_zigzag(-0x8000000000000000))
+ assert.Equal(t, i64(-0x8000000000000000), decode_zigzag(encode_zigzag(-0x8000000000000000)))
+
+ assert.Equal(t, u64(-2), encode_zigzag(0x7fffffffffffffff))
+ assert.Equal(t, i64(0x7fffffffffffffff), decode_zigzag(encode_zigzag(0x7fffffffffffffff)))
+}
+
+func TestDoubleConversion(t *testing.T) {
+ assert.Equal(t, uint64(0), encode_double(0.0))
+ assert.Equal(t, 0.0, decode_double(encode_double(0.0)), 0.0)
+
+ assert.Equal(t, uint64(0x3ff0000000000000), encode_double(1.0))
+ assert.Equal(t, 1.0, decode_double(encode_double(1.0)), 0.0)
+
+ assert.Equal(t, uint64(0xbff0000000000000), encode_double(-1.0))
+ assert.Equal(t, -1.0, decode_double(encode_double(-1.0)), 0.0)
+
+ assert.Equal(t, uint64(0x4000000000000000), encode_double(2.0))
+ assert.Equal(t, 2.0, decode_double(encode_double(2.0)), 0.0)
+
+ assert.Equal(t, uint64(0xc000000000000000), encode_double(-2.0))
+ assert.Equal(t, -2.0, decode_double(encode_double(-2.0)), 0.0)
+
+ // go is not IEEE 754 compliant
+ negZero := 0.0
+ negZero *= -1
+ assert.Equal(t, uint64(0x8000000000000000), encode_double(negZero))
+ assert.Equal(t, negZero, decode_double(encode_double(negZero)), 0.0)
+
+ assert.Equal(t, uint64(0x400c000000000000), encode_double(3.5))
+ assert.Equal(t, 3.5, decode_double(encode_double(3.5)), 0.0)
+
+ assert.Equal(t, uint64(0x40EFFFFC00000000), encode_double(65535.875))
+ assert.Equal(t, 65535.875, decode_double(encode_double(65535.875)), 0.0)
+}
+
+const (
+ TYPE_LIMIT = 8
+ META_LIMIT = 32
+ MAX_NUM_SIZE = 8
+)
+
+func TestTypeAndMetaMangling(t *testing.T) {
+ var ty Type
+ for ty = 0; ty < TYPE_LIMIT; ty++ {
+ var meta uint32
+ for meta = 0; meta < META_LIMIT; meta++ {
+ mangled := encode_type_and_meta(ty, meta)
+ assert.Equal(t, ty, decode_type(mangled))
+ assert.Equal(t, meta, decode_meta(mangled))
+ }
+ }
+}
+
+func verify_cmpr_int(t *testing.T, value int, expect []byte) {
+ out := newBinaryEncoder()
+ out.encode_cmpr_uint(uint32(value))
+ assert.Equal(t, expect, out.buf)
+
+ in := newBinaryDecoder(out.buf)
+ got := in.read_cmpr_int()
+ assert.Equal(t, value, got)
+ assert.Equal(t, false, in.failed)
+ assert.Equal(t, len(out.buf), in.pos)
+ fmt.Println("verify_cmpr_int OK:", value)
+}
+
+func TestCompressedInt(t *testing.T) {
+ var value int
+ var wanted []byte
+
+ value = 0
+ wanted = []byte{0}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 127
+ wanted = []byte{127}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 128
+ wanted = []byte{0x80, 1}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 16383
+ wanted = []byte{0xff, 127}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 16384
+ wanted = []byte{0x80, 0x80, 1}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 2097151
+ wanted = []byte{0xff, 0xff, 127}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 2097152
+ wanted = []byte{0x80, 0x80, 0x80, 1}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 268435455
+ wanted = []byte{0xff, 0xff, 0xff, 127}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 268435456
+ wanted = []byte{0x80, 0x80, 0x80, 0x80, 1}
+ verify_cmpr_int(t, value, wanted)
+
+ value = 0x7fff_ffff
+ wanted = []byte{0xff, 0xff, 0xff, 0xff, 7}
+ verify_cmpr_int(t, value, wanted)
+}
+
+func TestEncodingEmptySlime(t *testing.T) {
+ slime := NewSlime()
+ assert.Equal(t, false, slime.Get().Valid())
+ expect := []byte{
+ 0, // num symbols
+ 0, // nix
+ }
+ actual := BinaryEncode(slime)
+ assert.Equal(t, expect, actual)
+ decoded := BinaryDecode(actual)
+ root := decoded.Get()
+ assert.Equal(t, true, root.Valid())
+ assert.Equal(t, NIX, root.Type())
+}
+
+func verifyEncoding(t *testing.T, slime Slime, expect []byte) {
+ assert.Equal(t, expect, BinaryEncode(slime))
+ orig := JsonEncode(slime)
+ fmt.Println("orig:", orig)
+ decoded := BinaryDecode(expect)
+ got := JsonEncode(decoded)
+ fmt.Println(" got:", got)
+ assert.Equal(t, orig, got)
+}
+
+func TestEncodingSlimeHoldingASingleBasicValue(t *testing.T) {
+ var slime Slime
+ var expect []byte
+
+ slime = NewSlime()
+ slime.SetBool(false)
+ expect = []byte{0, byte(BOOL)}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetBool(true)
+ expect = []byte{0, byte(BOOL) | 8}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetLong(0)
+ expect = []byte{0, byte(LONG)}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetLong(13)
+ expect = []byte{0, byte(LONG) | 8, 13 * 2}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetLong(-123456789)
+ var ev uint64 = (2 * 123456789) - 1
+ b1 := byte(ev)
+ b2 := byte(ev >> 8)
+ b3 := byte(ev >> 16)
+ b4 := byte(ev >> 24)
+
+ expect = []byte{0, byte(LONG) | 32, b1, b2, b3, b4}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetDouble(0.0)
+ expect = []byte{0, byte(DOUBLE)}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetDouble(1.0)
+ expect = []byte{0, byte(DOUBLE) | 16, 0x3f, 0xf0}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetString("")
+ expect = []byte{0, byte(STRING), 0}
+
+ slime = NewSlime()
+ slime.SetString("fo")
+ expect = []byte{0, byte(STRING) | 24, 'f', 'o'}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ expect = []byte{0, byte(STRING), 26 * 2,
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ 'W', 'X', 'Y', 'Z'}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetData(emptyByteSlice)
+ expect = []byte{0, byte(DATA) | 8}
+ verifyEncoding(t, slime, expect)
+
+ slime = NewSlime()
+ slime.SetData([]byte{42, 133})
+ expect = []byte{0, byte(DATA) | 24, 42, 133}
+ verifyEncoding(t, slime, expect)
+}
+
+func TestEncodingSlimeArray(t *testing.T) {
+ slime := NewSlime()
+ cursor := slime.SetArray()
+ cursor.AddNix()
+ cursor.AddBool(true)
+ cursor.AddLong(42)
+ cursor.AddDouble(3.5)
+ cursor.AddString("string")
+ cursor.AddData([]byte{'d', 'a', 't', 'a'})
+ expect := []byte{
+ 0, // num symbols
+ byte(ARRAY) | 7*8, // value type and size
+ 0, // nix
+ byte(BOOL) | 8,
+ byte(LONG) | 8, 42 * 2,
+ byte(DOUBLE) | 16, 0x40, 0x0c, // 3.5
+ byte(STRING) | 7*8, 's', 't', 'r', 'i', 'n', 'g',
+ byte(DATA) | 5*8, 'd', 'a', 't', 'a',
+ }
+ verifyEncoding(t, slime, expect)
+}
+
+func TestEncodingSlimeObject(t *testing.T) {
+ slime := NewSlime()
+ cursor := slime.SetObject()
+ cursor.SetNix("a")
+ cursor.SetBool("bar", true)
+ cursor.SetLong("c", 42)
+ cursor.SetDouble("dblval", 3.5)
+ cursor.SetString("e", "string")
+ cursor.SetData("f", []byte{'d', 'a', 't', 'a'})
+ expect := []byte{
+ 6, // num symbols
+ 1, 'a', 3, 'b', 'a', 'r', 1, 'c', // symbol table
+ 6, 'd', 'b', 'l', 'v', 'a', 'l',
+ 1, 'e', 1, 'f',
+ byte(OBJECT) | 7*8, // value type and size
+ 0, byte(NIX),
+ 1, byte(BOOL) | 8,
+ 2, byte(LONG) | 8, 42 * 2,
+ 3, byte(DOUBLE) | 16, 0x40, 0x0c, // 3.5
+ 4, byte(STRING) | 7*8, 's', 't', 'r', 'i', 'n', 'g',
+ 5, byte(DATA) | 5*8, 'd', 'a', 't', 'a',
+ }
+ verifyEncoding(t, slime, expect)
+ slime = BinaryDecode(expect)
+ root := slime.Get()
+ assert.Equal(t, true, root.Field("a").Valid())
+}
+
+func TestEncodingComplexSlimeStructure(t *testing.T) {
+ slime := NewSlime()
+ c1 := slime.SetObject()
+ c1.SetLong("bar", 10)
+ c2 := c1.SetArray("foo")
+ c2.AddLong(20)
+ c3 := c2.AddObject()
+ c3.SetLong("answer", 42)
+ expect := []byte{
+ 3, // num symbols
+ 3, 'b', 'a', 'r',
+ 3, 'f', 'o', 'o',
+ 6, 'a', 'n', 's', 'w', 'e', 'r',
+ byte(OBJECT) | 3*8, // value type, size 2
+ 0, byte(LONG) | 8, 10 * 2,
+ 1, byte(ARRAY) | 3*8, // nested value type, size 2
+ byte(LONG) | 8, 20 * 2,
+ byte(OBJECT) | 2*8, // doubly nested value, size 1
+ 2, byte(LONG) | 8, 42 * 2,
+ }
+ verifyEncoding(t, slime, expect)
+}
+
+func TestEncodingSlimeReusingSymbols(t *testing.T) {
+ slime := NewSlime()
+ c1 := slime.SetArray()
+ c2a := c1.AddObject()
+ c2a.SetLong("foo", 10)
+ c2a.SetLong("bar", 20)
+ c2b := c1.AddObject()
+ c2b.SetLong("foo", 100)
+ c2b.SetLong("bar", 200)
+ expect := []byte{
+ 2, // num symbols
+ 3, 'b', 'a', 'r',
+ 3, 'f', 'o', 'o',
+ byte(ARRAY) | 3*8, // value type and size
+ byte(OBJECT) | 3*8, // nested value
+ 0, byte(LONG) | 1*8, 20 * 2, // bar
+ 1, byte(LONG) | 1*8, 10 * 2, // foo
+ byte(OBJECT) | 3*8, // nested value
+ 0, byte(LONG) | 2*8, 144, 1, // bar: 2*200 = 400 = 256 + 144
+ 1, byte(LONG) | 1*8, 100 * 2, // foo
+ }
+ verifyEncoding(t, slime, expect)
+}
+
+func TestDecodingSlimeWithDifferentSymbolOrder(t *testing.T) {
+ data := []byte{
+ 5, // num symbols
+ 1, 'd', 1, 'e', 1, 'f', 1, 'b', 1, 'c', // symbol table
+ byte(OBJECT) | 6*8, // value type and size
+ 3, byte(BOOL) | 1*8, // b
+ 1, byte(STRING) | 7*8, // e
+ 's', 't', 'r', 'i', 'n', 'g',
+ 4, byte(LONG) | 1*8, 5 * 2, // c
+ 0, byte(DOUBLE) | 2*8, 0x40, 0x0c, // d
+ 2, byte(DATA) | 5*8, // f
+ 'd', 'a', 't', 'a',
+ }
+ slime := BinaryDecode(data)
+ fmt.Println(" got:", JsonEncode(slime))
+ c := slime.Get()
+ assert.Equal(t, true, c.Valid())
+ assert.Equal(t, OBJECT, c.Type())
+ assert.Equal(t, 5, c.Fields())
+ assert.Equal(t, true, c.Field("b").AsBool())
+ assert.Equal(t, int64(5), c.Field("c").AsLong())
+ assert.Equal(t, 3.5, c.Field("d").AsDouble())
+ assert.Equal(t, "string", c.Field("e").AsString())
+ expd := []byte{'d', 'a', 't', 'a'}
+ assert.Equal(t, expd, c.Field("f").AsData())
+ assert.Equal(t, false, c.Entry(5).Valid()) // not ARRAY
+}
diff --git a/client/go/internal/slime/bool_value.go b/client/go/internal/slime/bool_value.go
new file mode 100644
index 00000000000..793608c7138
--- /dev/null
+++ b/client/go/internal/slime/bool_value.go
@@ -0,0 +1,25 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type BoolValue struct {
+ valueBase
+ v bool
+}
+
+func newBoolValue(value bool) *BoolValue {
+ return &BoolValue{v: value}
+}
+
+func (b *BoolValue) Valid() bool {
+ return true
+}
+
+func (b *BoolValue) AsBool() bool {
+ return b.v
+}
+
+func (b *BoolValue) Type() Type {
+ return BOOL
+}
diff --git a/client/go/internal/slime/cursor.go b/client/go/internal/slime/cursor.go
new file mode 100644
index 00000000000..1e3d03cd0f5
--- /dev/null
+++ b/client/go/internal/slime/cursor.go
@@ -0,0 +1,28 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type Cursor interface {
+ Inspector
+ MutableEntry(int) Cursor
+ MutableField(string) Cursor
+ // for arrays only:
+ AddNix() Inspector
+ AddBool(bool) Inspector
+ AddLong(int64) Inspector
+ AddDouble(float64) Inspector
+ AddString(string) Inspector
+ AddData([]byte) Inspector
+ AddArray() Cursor
+ AddObject() Cursor
+ // for objects only:
+ SetNix(string) Inspector
+ SetBool(string, bool) Inspector
+ SetLong(string, int64) Inspector
+ SetDouble(string, float64) Inspector
+ SetString(string, string) Inspector
+ SetData(string, []byte) Inspector
+ SetArray(string) Cursor
+ SetObject(string) Cursor
+}
diff --git a/client/go/internal/slime/data_value.go b/client/go/internal/slime/data_value.go
new file mode 100644
index 00000000000..1ac602952d7
--- /dev/null
+++ b/client/go/internal/slime/data_value.go
@@ -0,0 +1,25 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type DataValue struct {
+ valueBase
+ v []byte
+}
+
+func newDataValue(value []byte) *DataValue {
+ return &DataValue{v: value}
+}
+
+func (d *DataValue) Valid() bool {
+ return true
+}
+
+func (d *DataValue) AsData() []byte {
+ return d.v
+}
+
+func (d *DataValue) Type() Type {
+ return DATA
+}
diff --git a/client/go/internal/slime/double_value.go b/client/go/internal/slime/double_value.go
new file mode 100644
index 00000000000..d8c31626184
--- /dev/null
+++ b/client/go/internal/slime/double_value.go
@@ -0,0 +1,29 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type DoubleValue struct {
+ valueBase
+ v float64
+}
+
+func newDoubleValue(value float64) *DoubleValue {
+ return &DoubleValue{v: value}
+}
+
+func (d *DoubleValue) Valid() bool {
+ return true
+}
+
+func (d *DoubleValue) AsDouble() float64 {
+ return d.v
+}
+
+func (d *DoubleValue) AsLong() int64 {
+ return int64(d.v)
+}
+
+func (d *DoubleValue) Type() Type {
+ return DOUBLE
+}
diff --git a/client/go/internal/slime/inspector.go b/client/go/internal/slime/inspector.go
new file mode 100644
index 00000000000..76e19035ecf
--- /dev/null
+++ b/client/go/internal/slime/inspector.go
@@ -0,0 +1,20 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type Inspector interface {
+ Valid() bool
+ Type() Type
+ Entries() int // for arrays only
+ Fields() int // for objects only
+ AsBool() bool
+ AsLong() int64
+ AsDouble() float64
+ AsString() string
+ AsData() []byte
+ TraverseArray(ArrayTraverser)
+ TraverseObject(ObjectTraverser)
+ Entry(int) Inspector
+ Field(string) Inspector
+}
diff --git a/client/go/internal/slime/json_format.go b/client/go/internal/slime/json_format.go
new file mode 100644
index 00000000000..b6276480c4a
--- /dev/null
+++ b/client/go/internal/slime/json_format.go
@@ -0,0 +1,141 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+import (
+ "fmt"
+ "io"
+ "strings"
+)
+
+const (
+ dblquote byte = '"'
+ backslash byte = '\\'
+)
+
+type jsonEncoder struct {
+ out io.Writer
+ comma bool
+}
+
+func JsonEncode(s Slime) string {
+ var buf strings.Builder
+ codec := jsonEncoder{out: &buf}
+ codec.encode(s.Get())
+ return buf.String()
+}
+
+func JsonEncodeTo(s Slime, target io.Writer) {
+ codec := jsonEncoder{out: target}
+ codec.encode(s.Get())
+}
+
+func (e *jsonEncoder) Entry(idx int, value Inspector) {
+ if e.comma {
+ e.out.Write([]byte(", "))
+ }
+ e.encode(value)
+ e.comma = true
+}
+
+func (e *jsonEncoder) Field(name string, value Inspector) {
+ if e.comma {
+ e.out.Write([]byte(", "))
+ }
+ e.encodeSTRING(name)
+ e.out.Write([]byte(": "))
+ e.encode(value)
+ e.comma = true
+}
+
+func (e *jsonEncoder) encode(inspector Inspector) {
+ switch inspector.Type() {
+ case NIX:
+ e.encodeNIX()
+ case BOOL:
+ e.encodeBOOL(inspector.AsBool())
+ case LONG:
+ e.encodeLONG(inspector.AsLong())
+ case DOUBLE:
+ e.encodeDOUBLE(inspector.AsDouble())
+ case STRING:
+ e.encodeSTRING(inspector.AsString())
+ case DATA:
+ e.encodeDATA(inspector.AsData())
+ case ARRAY:
+ e.encodeARRAY(inspector)
+ case OBJECT:
+ e.encodeOBJECT(inspector)
+ }
+}
+
+func (e *jsonEncoder) encodeNIX() {
+ fmt.Fprintf(e.out, "%s", "null")
+}
+func (e *jsonEncoder) encodeBOOL(value bool) {
+ fmt.Fprintf(e.out, "%v", value)
+}
+func (e *jsonEncoder) encodeLONG(value int64) {
+ fmt.Fprintf(e.out, "%d", value)
+}
+
+func (e *jsonEncoder) encodeDOUBLE(value float64) {
+ fmt.Fprintf(e.out, "%f", value)
+}
+
+func (e *jsonEncoder) encodeSTRING(value string) {
+ buf := make([]byte, 0, len(value)*2)
+ buf = append(buf, dblquote)
+ for i := 0; i < len(value); i++ {
+ var c byte = value[i]
+ switch c {
+ case dblquote:
+ buf = append(buf, backslash)
+ buf = append(buf, dblquote)
+ case backslash:
+ buf = append(buf, backslash)
+ buf = append(buf, backslash)
+ case '\n':
+ buf = append(buf, backslash)
+ buf = append(buf, 'n')
+ default:
+ buf = append(buf, c)
+ }
+ }
+ buf = append(buf, dblquote)
+ e.out.Write(buf)
+}
+
+func hexDigit(digit byte) byte {
+ if digit < 10 {
+ return byte('0' + digit)
+ }
+ return byte('A' + digit - 10)
+}
+
+func (e *jsonEncoder) encodeDATA(value []byte) {
+ buf := make([]byte, 0, len(value)*2+2)
+ buf = append(buf, 'x')
+ for _, b := range value {
+ hv := (b >> 4) & 0xf
+ buf = append(buf, hexDigit(hv))
+ hv = b & 0xf
+ buf = append(buf, hexDigit(hv))
+ }
+ e.out.Write(buf)
+}
+
+func (e *jsonEncoder) encodeARRAY(inspector Inspector) {
+ e.out.Write([]byte("["))
+ e.comma = false
+ inspector.TraverseArray(e)
+ e.out.Write([]byte("]"))
+}
+
+func (e *jsonEncoder) encodeOBJECT(inspector Inspector) {
+ e.out.Write([]byte("{"))
+ e.comma = false
+ inspector.TraverseObject(e)
+ e.out.Write([]byte("}"))
+}
diff --git a/client/go/internal/slime/json_format_test.go b/client/go/internal/slime/json_format_test.go
new file mode 100644
index 00000000000..f3e3ca61ccb
--- /dev/null
+++ b/client/go/internal/slime/json_format_test.go
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package slime
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPrintJson(t *testing.T) {
+ slime := NewSlime()
+ o := slime.SetObject()
+ o.SetBool("a", true)
+ o.SetBool("b", false)
+ o.SetLong("c", 42)
+ o.SetLong("d", 0)
+ o.SetLong("e", -123456)
+ o.SetDouble("f", 2.75)
+ o.SetString("g", "x with\nembedded \"stuff\" like \\n")
+ got := o.Field("g").AsString()
+ assert.Equal(t,
+ `x with
+embedded "stuff" like \n`, got)
+ a := o.SetArray("h")
+ assert.Equal(t, 0, a.Entries())
+ a.AddLong(17)
+ o2 := o.SetObject("j")
+ assert.Equal(t, 0, o2.Fields())
+ o2.SetLong("jj", 987654321)
+ os.Stderr.Write([]byte("test JsonEncodeTo:\n>>>\n"))
+ JsonEncodeTo(slime, os.Stderr)
+ os.Stderr.Write([]byte("\n<<<\ntest JsonEncodeTo\n"))
+}
diff --git a/client/go/internal/slime/long_value.go b/client/go/internal/slime/long_value.go
new file mode 100644
index 00000000000..10fa582c5ce
--- /dev/null
+++ b/client/go/internal/slime/long_value.go
@@ -0,0 +1,29 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type LongValue struct {
+ valueBase
+ v int64
+}
+
+func newLongValue(value int64) *LongValue {
+ return &LongValue{v: value}
+}
+
+func (l *LongValue) Valid() bool {
+ return true
+}
+
+func (l *LongValue) AsLong() int64 {
+ return l.v
+}
+
+func (l *LongValue) AsDouble() float64 {
+ return float64(l.v)
+}
+
+func (l *LongValue) Type() Type {
+ return LONG
+}
diff --git a/client/go/internal/slime/nix_value.go b/client/go/internal/slime/nix_value.go
new file mode 100644
index 00000000000..57da619b547
--- /dev/null
+++ b/client/go/internal/slime/nix_value.go
@@ -0,0 +1,22 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type NixValue struct {
+ valueBase
+ valid bool
+}
+
+func (n *NixValue) Valid() bool {
+ return n.valid
+}
+
+func (n *NixValue) Type() Type {
+ return NIX
+}
+
+// actually constants:
+
+var ValidNix *NixValue = &NixValue{valid: true}
+var InvalidNix *NixValue = &NixValue{valid: false}
diff --git a/client/go/internal/slime/object_traverser.go b/client/go/internal/slime/object_traverser.go
new file mode 100644
index 00000000000..51ba9ab374e
--- /dev/null
+++ b/client/go/internal/slime/object_traverser.go
@@ -0,0 +1,8 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type ObjectTraverser interface {
+ Field(name string, value Inspector)
+}
diff --git a/client/go/internal/slime/object_value.go b/client/go/internal/slime/object_value.go
new file mode 100644
index 00000000000..a1ffca0011f
--- /dev/null
+++ b/client/go/internal/slime/object_value.go
@@ -0,0 +1,94 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+import (
+ "sort"
+)
+
+type ObjectValue struct {
+ valueBase
+ v map[string]Cursor
+}
+
+func newObjectValue() *ObjectValue {
+ return &ObjectValue{
+ v: make(map[string]Cursor),
+ }
+}
+
+func (o *ObjectValue) addElement(name string, elem Cursor) Cursor {
+ o.v[name] = elem
+ return elem
+}
+
+func (o *ObjectValue) Valid() bool {
+ return true
+}
+
+func (o *ObjectValue) Type() Type {
+ return OBJECT
+}
+
+func (o *ObjectValue) Fields() int {
+ return len(o.v)
+}
+
+func (o *ObjectValue) TraverseObject(traverser ObjectTraverser) {
+ keys := make([]string, 0, len(o.v))
+ for k := range o.v {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, name := range keys {
+ value := o.v[name]
+ traverser.Field(name, value)
+ }
+}
+
+func (o *ObjectValue) Field(name string) Inspector {
+ if r, ok := o.v[name]; ok {
+ return r
+ }
+ return InvalidNix
+}
+
+func (o *ObjectValue) MutableField(name string) Cursor {
+ if r, ok := o.v[name]; ok {
+ return r
+ }
+ return InvalidNix
+}
+
+func (o *ObjectValue) SetNix(name string) Inspector {
+ return o.addElement(name, ValidNix)
+}
+
+func (o *ObjectValue) SetBool(name string, value bool) Inspector {
+ return o.addElement(name, newBoolValue(value))
+}
+
+func (o *ObjectValue) SetLong(name string, value int64) Inspector {
+ return o.addElement(name, newLongValue(value))
+}
+
+func (o *ObjectValue) SetDouble(name string, value float64) Inspector {
+ return o.addElement(name, newDoubleValue(value))
+}
+
+func (o *ObjectValue) SetString(name string, value string) Inspector {
+ return o.addElement(name, newStringValue(value))
+}
+
+func (o *ObjectValue) SetData(name string, value []byte) Inspector {
+ return o.addElement(name, newDataValue(value))
+}
+
+func (o *ObjectValue) SetArray(name string) Cursor {
+ return o.addElement(name, newArrayValue())
+}
+
+func (o *ObjectValue) SetObject(name string) Cursor {
+ return o.addElement(name, newObjectValue())
+}
diff --git a/client/go/internal/slime/slime.go b/client/go/internal/slime/slime.go
new file mode 100644
index 00000000000..8adce333713
--- /dev/null
+++ b/client/go/internal/slime/slime.go
@@ -0,0 +1,63 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type Slime struct {
+ root Cursor
+}
+
+func NewSlime() Slime {
+ return Slime{InvalidNix}
+}
+
+func (s *Slime) Get() Cursor {
+ return s.root
+}
+
+func (s *Slime) SetNix() Cursor {
+ s.root = ValidNix
+ return s.root
+}
+
+func (s *Slime) SetBool(val bool) Cursor {
+ s.root = newBoolValue(val)
+ return s.root
+}
+
+func (s *Slime) SetLong(val int64) Cursor {
+ s.root = newLongValue(val)
+ return s.root
+}
+
+func (s *Slime) SetDouble(val float64) Cursor {
+ s.root = newDoubleValue(val)
+ return s.root
+}
+
+func (s *Slime) SetString(val string) Cursor {
+ s.root = newStringValue(val)
+ return s.root
+}
+
+func (s *Slime) SetData(val []byte) Cursor {
+ s.root = newDataValue(val)
+ return s.root
+}
+
+func (s *Slime) SetArray() Cursor {
+ s.root = newArrayValue()
+ return s.root
+}
+
+func (s *Slime) SetObject() Cursor {
+ s.root = newObjectValue()
+ return s.root
+}
+
+func (s *Slime) Wrap(name string) Cursor {
+ top := newObjectValue()
+ top.v[name] = s.root
+ s.root = top
+ return top
+}
diff --git a/client/go/internal/slime/string_value.go b/client/go/internal/slime/string_value.go
new file mode 100644
index 00000000000..90d5f627689
--- /dev/null
+++ b/client/go/internal/slime/string_value.go
@@ -0,0 +1,29 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type StringValue struct {
+ valueBase
+ v string
+}
+
+func newStringValue(value string) *StringValue {
+ return &StringValue{v: value}
+}
+
+func (s *StringValue) Valid() bool {
+ return true
+}
+
+func (s *StringValue) AsString() string {
+ return s.v
+}
+
+func (s *StringValue) AsData() []byte {
+ return []byte(s.v)
+}
+
+func (s *StringValue) Type() Type {
+ return STRING
+}
diff --git a/client/go/internal/slime/type.go b/client/go/internal/slime/type.go
new file mode 100644
index 00000000000..4b11744fe48
--- /dev/null
+++ b/client/go/internal/slime/type.go
@@ -0,0 +1,17 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type Type byte
+
+const (
+ NIX Type = iota
+ BOOL
+ LONG
+ DOUBLE
+ STRING
+ DATA
+ ARRAY
+ OBJECT
+)
diff --git a/client/go/internal/slime/value_base.go b/client/go/internal/slime/value_base.go
new file mode 100644
index 00000000000..74252837b21
--- /dev/null
+++ b/client/go/internal/slime/value_base.go
@@ -0,0 +1,37 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Author: arnej
+
+package slime
+
+type valueBase struct {
+}
+
+func (v valueBase) Entries() int { return 0 }
+func (v valueBase) Fields() int { return 0 }
+func (v valueBase) AsBool() bool { return false }
+func (v valueBase) AsLong() int64 { return 0 }
+func (v valueBase) AsDouble() float64 { return 0.0 }
+func (v valueBase) AsString() string { return "" }
+func (v valueBase) AsData() []byte { return emptyByteSlice }
+func (v valueBase) TraverseArray(ArrayTraverser) { return }
+func (v valueBase) TraverseObject(ObjectTraverser) { return }
+func (v valueBase) Entry(int) Inspector { return InvalidNix }
+func (v valueBase) Field(string) Inspector { return InvalidNix }
+func (v valueBase) MutableEntry(int) Cursor { return InvalidNix }
+func (v valueBase) MutableField(string) Cursor { return InvalidNix }
+func (v valueBase) AddNix() Inspector { return InvalidNix }
+func (v valueBase) AddBool(bool) Inspector { return InvalidNix }
+func (v valueBase) AddLong(int64) Inspector { return InvalidNix }
+func (v valueBase) AddDouble(float64) Inspector { return InvalidNix }
+func (v valueBase) AddString(string) Inspector { return InvalidNix }
+func (v valueBase) AddData([]byte) Inspector { return InvalidNix }
+func (v valueBase) AddArray() Cursor { return InvalidNix }
+func (v valueBase) AddObject() Cursor { return InvalidNix }
+func (v valueBase) SetNix(string) Inspector { return InvalidNix }
+func (v valueBase) SetBool(string, bool) Inspector { return InvalidNix }
+func (v valueBase) SetLong(string, int64) Inspector { return InvalidNix }
+func (v valueBase) SetDouble(string, float64) Inspector { return InvalidNix }
+func (v valueBase) SetString(string, string) Inspector { return InvalidNix }
+func (v valueBase) SetData(string, []byte) Inspector { return InvalidNix }
+func (v valueBase) SetArray(string) Cursor { return InvalidNix }
+func (v valueBase) SetObject(string) Cursor { return InvalidNix }