From 801b46f85d04fbfee38b88312419393e20340571 Mon Sep 17 00:00:00 2001 From: Arne Juul Date: Wed, 24 May 2023 17:04:46 +0000 Subject: [PATCH] add minimal port of Slime API --- client/go/internal/slime/array_traverser.go | 8 + client/go/internal/slime/array_value.go | 96 +++++ client/go/internal/slime/binary_decoder.go | 188 +++++++++ client/go/internal/slime/binary_encoder.go | 191 +++++++++ client/go/internal/slime/binary_format.go | 59 +++ .../go/internal/slime/binary_format_test.go | 378 ++++++++++++++++++ client/go/internal/slime/bool_value.go | 25 ++ client/go/internal/slime/cursor.go | 28 ++ client/go/internal/slime/data_value.go | 25 ++ client/go/internal/slime/double_value.go | 29 ++ client/go/internal/slime/inspector.go | 20 + client/go/internal/slime/json_format.go | 141 +++++++ client/go/internal/slime/json_format_test.go | 34 ++ client/go/internal/slime/long_value.go | 29 ++ client/go/internal/slime/nix_value.go | 22 + client/go/internal/slime/object_traverser.go | 8 + client/go/internal/slime/object_value.go | 94 +++++ client/go/internal/slime/slime.go | 63 +++ client/go/internal/slime/string_value.go | 29 ++ client/go/internal/slime/type.go | 17 + client/go/internal/slime/value_base.go | 37 ++ 21 files changed, 1521 insertions(+) create mode 100644 client/go/internal/slime/array_traverser.go create mode 100644 client/go/internal/slime/array_value.go create mode 100644 client/go/internal/slime/binary_decoder.go create mode 100644 client/go/internal/slime/binary_encoder.go create mode 100644 client/go/internal/slime/binary_format.go create mode 100644 client/go/internal/slime/binary_format_test.go create mode 100644 client/go/internal/slime/bool_value.go create mode 100644 client/go/internal/slime/cursor.go create mode 100644 client/go/internal/slime/data_value.go create mode 100644 client/go/internal/slime/double_value.go create mode 100644 client/go/internal/slime/inspector.go create mode 100644 client/go/internal/slime/json_format.go create mode 100644 client/go/internal/slime/json_format_test.go create mode 100644 client/go/internal/slime/long_value.go create mode 100644 client/go/internal/slime/nix_value.go create mode 100644 client/go/internal/slime/object_traverser.go create mode 100644 client/go/internal/slime/object_value.go create mode 100644 client/go/internal/slime/slime.go create mode 100644 client/go/internal/slime/string_value.go create mode 100644 client/go/internal/slime/type.go create mode 100644 client/go/internal/slime/value_base.go 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 }