ErrBadInt = errors.New("bad int value")
)
+// Decode a single YAC-encoded atom. Atom means that it does not decode
+// full lists, maps, blobs and may return types.EOC.
func Decode(buf []byte) (t types.Type, v any, off int, err error) {
off = 1
if len(buf) < 1 {
IsUTF8 = 0x40
)
+// Append an encoded EOC atom to the buf.
func EOCEncode(buf []byte) []byte {
return append(buf, byte(EOC))
}
+// Append an encoded NIL atom to the buf.
func NILEncode(buf []byte) []byte {
return append(buf, byte(NIL))
}
+// Append an encoded TRUE/FALSE atom to the buf.
func BoolEncode(buf []byte, v bool) []byte {
if v {
return append(buf, byte(True))
return append(buf, byte(False))
}
+// Append an encoded UUID atom to the buf.
func UUIDEncode(buf []byte, v uuid.UUID) []byte {
return append(append(buf, byte(UUID)), v[:]...)
}
return BinEncode(nil, buf)
}
+// Append an encoded +INT atom to the buf.
func UIntEncode(buf []byte, v uint64) []byte {
return append(buf, append([]byte{byte(PInt)}, atomUintEncode(v)...)...)
}
+// Append an encoded -INT atom to the buf if v is negative.
+// +INT atom otherwise, same as UIntEncode.
func IntEncode(buf []byte, v int64) []byte {
if v >= 0 {
return UIntEncode(buf, uint64(v))
atomUintEncode(uint64(-(v+1)))...)...)
}
+// Append an encoded ±INT atom to the buf.
func BigIntEncode(buf []byte, v *big.Int) []byte {
- // TODO: fallback to U?IntEncode for small values
if v.Cmp(bigIntZero) >= 0 {
return append(buf, BinEncode([]byte{byte(PInt)}, v.Bytes())...)
}
return append(buf, BinEncode([]byte{byte(NInt)}, v.Bytes())...)
}
+// Append an encoded LIST atom to the buf.
+// You have to manually terminate it with EOCEncode.
func ListEncode(buf []byte) []byte {
return append(buf, byte(List))
}
+// Append an encoded MAP atom to the buf.
+// You have to manually terminate it with EOCEncode.
func MapEncode(buf []byte) []byte {
return append(buf, byte(Map))
}
+// Append an encoded BLOB atom to the buf.
+// You have to manually provide necessary chunks and
+// properly terminate it with BinEncode.
func BlobEncode(buf []byte, chunkLen int) []byte {
l := make([]byte, 9)
l[0] = byte(Blob)
return append(append(append(buf, b), l...), data...)
}
+// Append an encoded STR atom to the buf.
func StrEncode(buf []byte, str string) []byte {
return atomStrEncode(buf, []byte(str), true)
}
+// Append an encoded BIN atom to the buf.
func BinEncode(buf, bin []byte) []byte {
return atomStrEncode(buf, bin, false)
}
+// Append an encoded CHUNK atom to the buf.
+// That is basically an appended NIL with the chunk value.
func ChunkEncode(buf, chunk []byte) []byte {
return append(append(buf, byte(NIL)), chunk...)
}
+// Append an encoded TAI64* atom to the buf.
func TAI64Encode(buf, tai []byte) []byte {
switch len(tai) {
case 8:
}
}
+// Append an encoded raw atom's value to the buf.
func RawEncode(buf []byte, raw Raw) []byte {
return append(append(buf, byte(raw.T)), raw.V...)
}
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
package atom
import (
"fmt"
)
+// Raw atom storage, keeping the tag T and contents V after it.
+// Used for keeping data that can not be represented in native Go types.
type Raw struct {
V []byte
T Type
import "fmt"
+// BLOB object, that keeps data splitted on ChunkLen chunks.
type Blob struct {
Chunks [][]byte
ChunkLen int
"time"
"github.com/google/uuid"
+
"go.cypherpunks.su/yac/gyac"
"go.cypherpunks.su/yac/gyac/atom"
)
"github.com/google/uuid"
"go.cypherpunks.su/tai64n/v4"
+
"go.cypherpunks.su/yac/gyac/atom"
)
ErrUnexpectedEOC = errors.New("unexpected EOC")
)
+// Item is the base object, holding the type and corresponding value.
+// Depending on the type, you have to type assert the value.
+// - EOC, NIL holds nothing
+// - Bool holds bool
+// - UUID holds uuid.UUID
+// - UInt holds uint64
+// - Int holds negative int64
+// - BigInt holds *big.Int
+// - List holds []Item
+// - Map holds map[string]Item
+// - Blob holds Blob
+// - Float (currently) holds atom.Raw
+// - TAI64 holds []byte with with the TAI64 in external format.
+// Look at its length to determine exact value
+// - Bin holds []byte
+// - Str holds string
+// - Raw holds the atom.Raw
type Item struct {
V any
T types.Type
return
}
-func Decode(buf []byte) (item *Item, tail []byte, err error) {
+// Decode single YAC-encoded data item. Remaining data will be kept in tail.
+func Decode(buf []byte) (item Item, tail []byte, err error) {
return decode(buf, true, false, 0)
}
--- /dev/null
+// YAC (http://www.yac.cypherpunks.su) is yet another binary
+// serialisation encoding format. It is aimed to be lightweight in terms
+// of CPU, memory, storage and codec implementation size usage. YAC is
+// deterministic and streamable. It supports wide range of data types,
+// making it able to transparently replace JSON.
+package gyac
"go.cypherpunks.su/yac/gyac/types"
)
+// Encode an item appending to the buf.
func (item Item) Encode(buf []byte) []byte {
switch item.T {
case types.NIL:
return
}
+// Create an Item from native Go type, for its future encoding.
+// Allowable types:
+// - [*]atom.Raw
+// - [*]Blob
+// - *big.Int
+// - []any
+// - []byte -- will be converted to binary string
+// - bool
+// - int, int8, int16, int32, int64
+// - map[string]any
+// - nil
+// - string
+// - struct -- will be interpreted as map[string]any
+// - time.Time -- will be converted either to
+// TAI64 (if nanoseconds=0), or TAI64N
+// - uint, uint8, uint16, uint32, uint64
+// - uuid.UUID
func FromGo(v any) Item {
if v == nil {
return Item{T: types.NIL}
"go.cypherpunks.su/yac/gyac/types"
)
+// Decode YAC-encoded data to the dst structure.
+// It will return an error if decoded data is not map.
func Decode(dst any, raw []byte) (tail []byte, err error) {
var item gyac.Item
item, tail, err = gyac.Decode(raw)
"github.com/mitchellh/mapstructure"
)
+// Fill up dst structure with the contents taken from the src map.
func FromMap(dst any, src map[string]any) error {
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: dst, TagName: "yac",
package gyac
+// Bitewise sorting by length first.
type ByLenFirst []string
func (a ByLenFirst) Len() int {
"go.cypherpunks.su/yac/gyac/types"
)
+// Convert an item to various native Go types, atom.Raw, Blob, uuid.UUID.
+// Pay attention that f TAI equals to leap second, then it is converted to Raw.
func (item Item) ToGo() any {
switch item.T {
case types.NIL: