fuller implementation of chunk and unmarshal

This commit is contained in:
Emilis 2022-08-05 19:53:44 +00:00
parent 3004475000
commit 9514f9c374
8 changed files with 598 additions and 325 deletions

View File

@ -7,7 +7,7 @@ func Map[T, V any](c Vector[T], f func(T) V) Vector[V] {
out[index] = f(c[index])
}
return From(out)
return From(out...)
}
func Filter[T any](v Vector[T], f func(T) bool) Vector[T] {
@ -27,7 +27,7 @@ func Take[T any](v Vector[T], howMany int) Vector[T] {
if len(v) < howMany {
howMany = len(v)
}
return From(v[:howMany-1])
return From(v[:howMany-1]...)
}
func Any[T any](v Vector[T], f func(T) bool) bool {
@ -39,6 +39,15 @@ func Any[T any](v Vector[T], f func(T) bool) bool {
return false
}
func AnyOf[T comparable](v Vector[T], t T) bool {
for _, vT := range v {
if vT == t {
return true
}
}
return false
}
type numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64
}

View File

@ -16,7 +16,7 @@ func WithCap[T any](capacity int) Vector[T] {
return make(Vector[T], 0, capacity)
}
func From[T any](v []T) Vector[T] {
func From[T any](v ...T) Vector[T] {
return Vector[T](v)
}
@ -45,7 +45,7 @@ func (v Vector[T]) Append(t ...T) Vector[T] {
}
func (v Vector[T]) Remove(index int) Vector[T] {
return From(append(v[:index], v[index+1:]...))
return From(append(v[:index], v[index+1:]...)...)
}
// Faster remove function that can be used on vectors where ordering doesn't matter
@ -110,12 +110,12 @@ func (v Vector[T]) Sub(start, end int) Vector[T] {
v.panicIndex(end)
}
if start < 0 {
return From(v[:end])
return From(v[:end]...)
}
if end < 0 {
return From(v[start:])
return From(v[start:]...)
}
return From(v[start:end])
return From(v[start:end]...)
}
func (v Vector[T]) Filter(f func(T) bool) Vector[T] {

View File

@ -59,18 +59,18 @@ func TestVector(t *testing.T) {
}
},
"append": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3}).Append(4, 5, 6)
v := coll.From([]int{1, 2, 3}...).Append(4, 5, 6)
that.Equal(6, len(v))
that.EqualValues([]int{1, 2, 3, 4, 5, 6}, v)
},
"getsoft": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
v := coll.From([]int{1, 2, 3}...)
that.Equal(2, v.GetSoft(1))
that.Zero(v.GetSoft(-1000))
that.Zero(v.GetSoft(1000))
},
"get": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
v := coll.From([]int{1, 2, 3}...)
that.Equal(2, v.Get(1))
that.Panics(func() {
v.Get(-1000)
@ -80,7 +80,7 @@ func TestVector(t *testing.T) {
})
},
"clone": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
v := coll.From([]int{1, 2, 3}...)
v1 := v.Clone().Set(1, 1)
that.Equal(3, len(v1))
@ -88,7 +88,7 @@ func TestVector(t *testing.T) {
that.EqualValues([]int{1, 2, 3}, v)
},
"set": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
v := coll.From([]int{1, 2, 3}...)
v1 := v.Clone().Set(1, 1)
that.Equal(3, len(v1))
@ -110,7 +110,7 @@ func TestVector(t *testing.T) {
})
},
"sub": func(that *require.Assertions) {
v := coll.From([]int{0, 1, 2, 3, 4, 5, 6})
v := coll.From([]int{0, 1, 2, 3, 4, 5, 6}...)
v1 := v.Clone().Sub(2, 4)
that.Equal(2, len(v1))
@ -139,13 +139,13 @@ func TestVector(t *testing.T) {
})
},
"filter": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3, 4, 5, 6})
v := coll.From([]int{1, 2, 3, 4, 5, 6}...)
v = v.Filter(func(i int) bool { return i%2 == 0 })
that.Len(v, 3)
},
"any": func(that *require.Assertions) {
that.True(coll.From([]int{1, 2, 3, 4, 5, 6}).Any(func(i int) bool { return i == 3 }))
that.False(coll.From([]int{1, 2, 3, 4, 5, 6}).Any(func(i int) bool { return i == 666 }))
that.True(coll.From([]int{1, 2, 3, 4, 5, 6}...).Any(func(i int) bool { return i == 3 }))
that.False(coll.From([]int{1, 2, 3, 4, 5, 6}...).Any(func(i int) bool { return i == 666 }))
},
"take": func(that *require.Assertions) {
that.EqualValues([]int{1, 2, 3}, coll.From([]int{1, 2, 3, 4, 5, 6}).Take(3))

View File

@ -3,16 +3,22 @@ package chunk
import (
"errors"
"fmt"
"unicode"
"unicode/utf8"
"sectorinf.com/emilis/flabk/pkg/coll"
)
const (
escapeChar = '\\'
unicodeEscape = 'u'
)
var (
ErrNoMatch = errors.New("no matching")
ErrMapMemberNotString = errors.New("map member not a string")
ErrUnexpectedSymbol = errors.New("unexpected symbol")
ErrMissingValue = errors.New("missing value")
ErrEscapeAtEnd = errors.New("escape character at the end of value")
ErrIncompleteEscape = errors.New("incomplete unicode escape sequence")
)
type Chunk struct {
@ -24,72 +30,120 @@ type Chunk struct {
globRight int
}
func (c Chunk) LeftByte() byte {
return c.vector[c.posLeft]
}
func (c Chunk) Null() bool {
return (c.posRight+1)-c.posLeft >= 4 && c.vector[c.posLeft] == 'n' && c.vector[c.posLeft+1] == 'u' && c.vector[c.posLeft+2] == 'l' && c.vector[c.posLeft+3] == 'l'
}
func (c Chunk) LeftPosf(format string, args ...any) error {
return fmt.Errorf(fmt.Sprintf("[%d] %s", c.globLeft, format), args...)
}
// Match finds the matching closer to the symbol at the left position and sets the
// right position to this index
func (c Chunk) Match() (Chunk, error) {
s, ok := matchers[c.vector[c.posLeft]]
start := c.vector[c.posLeft]
matcher, ok := matchers[start]
if !ok {
panic(fmt.Sprintf("Match called on %c with no matcher defined", c.vector[c.posLeft]))
}
if s.StartFromRight {
for c.posRight > 0 {
if c.vector[c.posRight] == s.MatchByte {
return c, nil
for index := c.posLeft + 1; index < len(c.vector); index++ {
if start != '"' && c.vector[index] == start {
sub, err := c.Child(index, len(c.vector)).Match()
if err != nil {
return c, fmt.Errorf("[%d] child %w", c.globLeft+(c.posLeft-index), err)
}
c.posRight--
c.globRight--
index += sub.posRight
continue
}
} else {
for index := c.posLeft + 1; index < len(c.vector); index++ {
if c.vector[index] == s.MatchByte {
c.posRight = index
c.globRight += (c.posLeft + 1) - index
return c, nil
if c.vector[index] == escapeChar {
if index+1 == len(c.vector) {
return c, fmt.Errorf("[%d] %w", c.globLeft+(c.posLeft-index), ErrEscapeAtEnd)
}
if c.vector[index+1] == unicodeEscape {
if index+6 >= len(c.vector) {
return c, fmt.Errorf("[%d] %w", c.globLeft+(c.posLeft+index), ErrIncompleteEscape)
}
index += 5
continue
}
index++
continue
}
if c.vector[index] == matcher {
c.globRight -= c.posRight - index
c.posRight = index
return c, nil
}
}
return c, fmt.Errorf("%w %c", ErrNoMatch, c.vector[c.posLeft])
return c, c.LeftPosf("%w %c", ErrNoMatch, c.vector[c.posLeft])
}
func (c Chunk) Copy() Chunk {
return c
}
func (c Chunk) Left() byte {
return c.vector[c.posLeft]
}
// Sub returns an inclusive subchunk of [left;right]
func (c Chunk) Sub() Chunk {
return New(c.vector[c.posLeft : c.posRight+1])
c = c.Child(c.posLeft, c.posRight+1)
// To be inclusive we incremented posRight above, so
// to restore the original position, we must decrement
// it here before returning. Only if we have room to decrement it
if c.posRight > 0 {
c.posRight--
c.globRight--
}
return c
}
// CookieCutter returns a subchunk of (left;right)
func (c Chunk) CookieCutter() Chunk {
return New(c.vector[c.posLeft+1 : c.posRight])
return c.Child(c.posLeft+1, c.posRight)
}
func spaceAtIndex(v coll.Vector[byte], index int) bool {
return v[index] == 0x9 || v[index] == 0xa || v[index] == 0xd || v[index] == 0x20
}
func (c Chunk) AtSpace() bool {
r, _ := utf8.DecodeRune(c.vector[c.posLeft:])
return unicode.IsSpace(r)
return spaceAtIndex(c.vector, c.posLeft)
}
func (c Chunk) Seek() Chunk {
for c.posLeft < len(c.vector) && c.AtSpace() {
c.globLeft++
c.posLeft++
}
return c
}
// Step increments the left position by 1 if b
// is the byte at the current left position
func (c Chunk) StepIf(b byte) Chunk {
if c.vector[c.posLeft] != b {
return c
}
c.posLeft++
c.globLeft++
return c
}
func (c Chunk) ValueEnd() (Chunk, error) {
switch c.vector[c.posLeft] {
case '"', '{', '[':
return c.Match()
default:
for index := c.posLeft; index <= c.posRight; index++ {
if c.vector[index] == ',' {
return New(c.vector[c.posLeft:index]), nil
if c.vector[index] == ',' || spaceAtIndex(c.vector, index) {
if index == c.posLeft {
return c, c.LeftPosf("%w", ErrMissingValue)
}
return c.Child(c.posLeft, index), nil
}
}
return c, nil
@ -100,6 +154,7 @@ func (c Chunk) ValueEnd() (Chunk, error) {
func (c Chunk) Skip() Chunk {
if c.posLeft+1 < len(c.vector) {
c.posLeft++
c.globLeft++
}
return c.Seek()
}
@ -110,18 +165,10 @@ type MatchRule struct {
}
var (
matchers = map[byte]MatchRule{
'{': {
MatchByte: '}',
StartFromRight: true,
},
'[': {
MatchByte: ']',
StartFromRight: true,
},
'"': {
MatchByte: '"',
},
matchers = map[byte]byte{
'{': '}',
'[': ']',
'"': '"',
}
)
@ -131,9 +178,10 @@ func New(v []byte) Chunk {
posRight = 0
}
return Chunk{
vector: coll.Vector[byte](v),
posLeft: 0,
posRight: posRight,
vector: coll.Vector[byte](v),
posLeft: 0,
posRight: posRight,
globRight: posRight,
}
}
@ -142,31 +190,14 @@ func (c Chunk) Child(left, right int) Chunk {
return Chunk{
vector: sub,
posLeft: 0,
posRight: len(sub),
posRight: len(sub) - 1,
globLeft: (c.globLeft - c.posLeft) + left,
globRight: (c.globRight - c.posRight) + right,
globRight: (c.globRight - c.posRight) + (right - 1),
}
}
type ParseFunc[T any] func(T) (T, error)
func Parse[T any](c Chunk) ParseFunc[T] {
switch c.vector[c.posLeft] {
case '{':
return ParseMap[T](c.CookieCutter())
default:
panic("not implemented")
}
// return can be:
// * {}
// * []
// * ""
// * 123
// * true
// * false
// * null
}
type Row struct {
Name string
Value Chunk
@ -177,47 +208,47 @@ func (c Chunk) String() string {
}
func (c Chunk) Row() (Row, error) {
c = c.Seek()
if c.vector[c.posLeft] != '"' {
return Row{}, fmt.Errorf("%w: %c", ErrMapMemberNotString, c.vector[c.posLeft])
return Row{}, c.LeftPosf("%w: %c", ErrMapMemberNotString, c.vector[c.posLeft])
}
name, err := c.Match()
if err != nil {
return Row{}, fmt.Errorf("match: %w", err)
return Row{}, c.LeftPosf("match: %w", err)
}
postName := c.Copy()
postName.posLeft = name.posRight
postName.globLeft = name.globRight
postName = postName.Skip()
// Next we must get a :
if postName.vector[postName.posLeft] != ':' {
return Row{}, fmt.Errorf("%w '%c', expected ':'", ErrUnexpectedSymbol, postName.vector[postName.posLeft])
return Row{}, postName.LeftPosf("%w '%c', expected ':'", ErrUnexpectedSymbol, postName.vector[postName.posLeft])
}
value, err := postName.Skip().ValueEnd()
if err != nil {
return Row{}, fmt.Errorf("value: %w", err)
return Row{}, postName.LeftPosf("value: %w", err)
}
return Row{
Name: name.String(),
Name: name.String()[1 : name.posRight-name.posLeft],
Value: value,
}, nil
}
// After returns the chunk with its left position, if possible,
// right after the global right position of v
func (c Chunk) After(v Chunk) Chunk {
c.posLeft = v.posLeft
// Add two, as one is for dealing with right side being exclusive
// in slice indexes, and another one to go on to the next
offset := (v.globRight - c.globLeft) + 1
// Then, make sure we don't go too far
if c.posLeft+offset >= len(c.vector) {
offset--
}
c.posLeft += offset
c.globLeft += offset
return c
}
func ParseMap[T any](c Chunk) ParseFunc[T] {
return func(t T) (T, error) {
// mapper := parse.GetMap(t)
// for {
// row, err := c.Row()
// if err != nil {
// return t, err
// }
// }
panic("todo")
}
func (c Chunk) EOF() bool {
return c.posLeft >= len(c.vector)-1
}

View File

@ -1,37 +1,108 @@
package chunk_test
package chunk
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"sectorinf.com/emilis/flabk/pkg/ld/internal/parse/chunk"
"sectorinf.com/emilis/flabk/pkg/coll"
)
func TestSpace(t *testing.T) {
tests := []byte{0x9, 0xa, 0xd, 0x20}
that := require.New(t)
ch := chunk.New([]byte(" hello world"))
that.True(ch.AtSpace())
for _, test := range tests {
b := []byte("xhello world")
b[0] = test
that.True(New(b).AtSpace())
}
}
func TestParseMap(t *testing.T) {
type Hello struct {
Hello string
func TestAlign(t *testing.T) {
tests := map[string]struct {
c func() Chunk
v func(Chunk) Chunk
}{
"same-size": {
c: func() Chunk {
c := New(coll.Vector[byte](`{"heres some test data": "hi mom"}`))
c.posLeft = 15
c.globLeft = 15
return c
},
v: func(c Chunk) Chunk {
c.posLeft = 20
c.globLeft = 20
c.globRight = 22
c.posRight = 22
return c
},
},
"cookie-cutter": {
c: func() Chunk {
c := New(coll.Vector[byte](`{"heres some test data": "hi mom"}`))
c.posLeft = 15
c.globLeft = 15
return c
},
v: func(c Chunk) Chunk {
return c.CookieCutter().CookieCutter()
},
},
"no-change": {
c: func() Chunk {
c := New(coll.Vector[byte](`{"heres some test data": "hi mom"}`))
c.posLeft = 15
c.globLeft = 15
return c
},
v: func(c Chunk) Chunk {
c.posRight = 15
c.globRight = 15
return c.Sub()
},
},
"contains": {
c: func() Chunk {
return New(coll.Vector[byte](`0123456789`))
},
v: func(c Chunk) Chunk {
return c.Child(5, 8)
},
},
}
ch := chunk.New([]byte(`"Hello": "world"`))
h, err := chunk.ParseMap[Hello](ch)(Hello{})
if err != nil {
panic(err)
for name, test := range tests {
test := test
t.Run(name, func(tt *testing.T) {
that := require.New(tt)
c := test.c()
v := test.v(c)
out := c.After(v)
that.Equal(v.globRight+1, out.globLeft)
that.Equal(string(c.vector[v.globRight+1]), string(out.LeftByte()))
})
}
fmt.Println(h)
}
func TestChild(t *testing.T) {
type Hello struct {
Hello string
}
ch := chunk.New([]byte(`"Hello": "world"`))
child := ch.Child(5, 10)
fmt.Println(child)
func TestRow(t *testing.T) {
c := New(coll.Vector[byte](`{
"heres some test data": "hi mom",
"another field": "get it right",
"integer": -12345
}`))
that := require.New(t)
row, err := c.CookieCutter().Seek().Row()
that.NoError(err)
that.Equal("heres some test data", row.Name)
that.Equal(`"hi mom"`, row.Value.String())
row, err = c.After(row.Value).StepIf(',').Seek().Row()
that.NoError(err)
that.Equal("another field", row.Name)
that.Equal(`"get it right"`, row.Value.String())
row, err = c.After(row.Value).StepIf(',').Seek().Row()
that.NoError(err)
that.Equal("integer", row.Name)
that.Equal("-12345", row.Value.String())
that.True(c.After(row.Value).Seek().EOF())
}

View File

@ -1,54 +1,200 @@
package parse
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"sectorinf.com/emilis/flabk/pkg/coll"
"sectorinf.com/emilis/flabk/pkg/ld/internal/consts"
"sectorinf.com/emilis/flabk/pkg/ld/internal/parse/chunk"
)
type LazyMapFunc func(name string, value string)
const (
memberSeparator = ','
)
func GetMap(v any) LazyMapFunc {
val := reflect.ValueOf(v)
// typ := reflect.TypeOf(v)
switch val.Kind() {
case reflect.Map:
return func(name string, value string) {
// val.SetMapIndex(reflect.ValueOf(name), value)
}
case reflect.Struct:
// fields := GetStructFields(val, typ)
return func(name, value string) {
// val, ok := fields[name]
// if ok {
// val.Set(value)
// }
}
default:
panic("wrong")
var (
ErrNotArray = errors.New("value is not an array")
ErrNotMap = errors.New("value is not a map")
)
var (
boolVals = map[string]bool{
"true": true,
"false": false,
}
determineMap = func() map[byte]coll.Vector[reflect.Kind] {
m := map[byte]coll.Vector[reflect.Kind]{
'"': coll.From(reflect.String),
'[': coll.From(reflect.Array, reflect.Slice),
'{': coll.From(reflect.Map, reflect.Struct),
'-': coll.From(reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64),
't': coll.From(reflect.Bool),
'f': coll.From(reflect.Bool),
}
numbers := coll.From(
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint64,
reflect.Uint32,
reflect.Uint16,
reflect.Uint8,
reflect.Float32,
reflect.Float64,
)
for index := byte('0'); index <= '9'; index++ {
m[index] = numbers
}
return m
}()
sizes = map[reflect.Kind]int{
// Int and Uint values stolen from the [math] package, as the intSize
// constant is unexported
reflect.Int: 32 << (^uint(0) >> 63), // 32 or 64
reflect.Uint: 32 << (^uint(0) >> 63), // 32 or 64
reflect.Int64: 64,
reflect.Int32: 32,
reflect.Int16: 16,
reflect.Int8: 8,
reflect.Uint64: 64,
reflect.Uint32: 32,
reflect.Uint16: 16,
reflect.Uint8: 8,
reflect.Float32: 32,
reflect.Float64: 64,
}
)
func SetMap(val reflect.Value, typ reflect.Type, c chunk.Chunk) error {
if c.LeftByte() != '{' {
return c.LeftPosf("%w", ErrNotMap)
}
fields := GetStructFields(val, typ)
c = c.CookieCutter().Seek()
for !c.EOF() {
row, err := c.Row()
if err != nil {
return fmt.Errorf("row: %w", err)
}
field, ok := fields[row.Name]
if ok {
if err := SetValue(field, field.Type(), row.Value); err != nil {
return fmt.Errorf("set: %w", err)
}
}
// Shift c
c = c.After(row.Value).StepIf(memberSeparator).Seek()
}
return nil
}
func ParseAsValue(v string, valType reflect.Type) (any, error) {
// Might not be necessary to trim
v = strings.TrimSpace(v)
switch valType.Kind() {
case reflect.String:
return v[1 : len(v)-1], nil
case reflect.Bool:
b, err := strconv.ParseBool(strings.ToLower(v))
if err != nil {
fmt.Errorf("boolean: %w", err)
}
return b, nil
case reflect.Struct, reflect.Map:
panic("todo")
default:
panic("todo")
func SetArray(val reflect.Value, typ reflect.Type, c chunk.Chunk) error {
if c.LeftByte() != '[' {
return c.LeftPosf("%w", ErrNotArray)
}
c = c.CookieCutter().Seek()
elems := coll.New[chunk.Chunk]()
for !c.EOF() {
element, err := c.ValueEnd()
if err != nil {
return fmt.Errorf("getting array elem: %w", err)
}
elems = elems.Push(element)
c = c.After(element).StepIf(memberSeparator).Seek()
}
elementType := typ.Elem()
arrayType := reflect.ArrayOf(len(elems), elementType)
array := reflect.New(arrayType).Elem()
for index, elem := range elems {
if err := SetValue(array.Index(index), elementType, elem); err != nil {
return fmt.Errorf("%w at array index [%d]", err, index)
}
}
if val.Kind() == reflect.Slice {
array = array.Slice(0, len(elems))
}
val.Set(array)
return nil
}
// Determines what [reflect.Kind] of object the given [chunk.Chunk] is
func Determine(c chunk.Chunk) coll.Vector[reflect.Kind] {
k, ok := determineMap[c.LeftByte()]
if !ok {
return coll.From(reflect.Invalid)
}
return k
}
func SetValue(val reflect.Value, typ reflect.Type, c chunk.Chunk) error {
if c.Null() {
return nil
}
if val.Kind() == reflect.Pointer {
if val.IsNil() {
val.Set(reflect.New(typ.Elem()))
}
return SetValue(val.Elem(), typ.Elem(), c)
}
kinds := Determine(c)
if !coll.AnyOf(kinds, val.Kind()) {
return fmt.Errorf("kind %s not found in appropriate list %s for value [%s]", val.Kind(), kinds, c)
}
switch val.Kind() {
case reflect.Map, reflect.Struct:
return SetMap(val, typ, c)
case reflect.String:
val.SetString(Escape(c.CookieCutter()))
case reflect.Bool:
b, ok := boolVals[c.String()]
if !ok {
return fmt.Errorf("value [%s] is not a boolean", c.String())
}
val.SetBool(b)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, err := strconv.ParseInt(c.String(), 10, sizes[val.Kind()])
if err != nil {
return fmt.Errorf("value [%s] is not an %s", c.String(), val.Kind().String())
}
val.SetInt(i)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u, err := strconv.ParseUint(c.String(), 10, sizes[val.Kind()])
if err != nil {
return fmt.Errorf("value [%s] is not an %s", c.String(), val.Kind().String())
}
val.SetUint(u)
case reflect.Float32, reflect.Float64:
f, err := strconv.ParseFloat(c.String(), sizes[val.Kind()])
if err != nil {
return fmt.Errorf("value [%s] is not an %s", c.String(), val.Kind().String())
}
val.SetFloat(f)
case reflect.Array, reflect.Slice:
return SetArray(val, typ, c)
default:
fmt.Println(val.Kind().String())
panic("unsupported")
}
return nil
}
func Escape(v chunk.Chunk) string {
// TODO: string escaping
return v.String()
}
func GetStructFields(val reflect.Value, typ reflect.Type) map[string]reflect.Value {

View File

@ -1 +1,17 @@
package ld
import (
"fmt"
"reflect"
"sectorinf.com/emilis/flabk/pkg/ld/internal/parse"
"sectorinf.com/emilis/flabk/pkg/ld/internal/parse/chunk"
)
func Unmarshal[T any](v []byte) (T, error) {
ptr := new(T)
if err := parse.SetValue(reflect.ValueOf(ptr), reflect.TypeOf(ptr), chunk.New(v)); err != nil {
return *ptr, fmt.Errorf("unmarshal: %w", err)
}
return *ptr, nil
}

View File

@ -1,192 +1,192 @@
package ld_test
// import (
// "encoding/json"
// "fmt"
// "math"
// "testing"
// "time"
import (
"encoding/json"
"fmt"
"math"
"testing"
"time"
// "sectorinf.com/emilis/flabk/pkg/asld"
// "sectorinf.com/emilis/flabk/pkg/epk"
// "github.com/stretchr/testify/require"
// )
"github.com/stretchr/testify/require"
"sectorinf.com/emilis/flabk/pkg/epk"
"sectorinf.com/emilis/flabk/pkg/ld"
)
// type testObj struct {
// String string `asld:"string" json:"string"`
// NoTag string
// Int int
// Int8 int8
// Int16 int16
// Int32 int32
// Int64 int64
// Uint uint
// Uint8 uint8
// Uint16 uint16
// Uint32 uint32
// Uint64 uint64
// Float32 float32
// Float64 float64
// // Complex64 complex64
// // Complex128 complex128
// IntPtr *int
// Int8Ptr *int8
// Int16Ptr *int16
// Int32Ptr *int32
// Int64Ptr *int64
// UintPtr *uint
// Uint8Ptr *uint8
// Uint16Ptr *uint16
// Uint32Ptr *uint32
// Uint64Ptr *uint64
// Float32Ptr *float32
// Float64Ptr *float64
// // Complex64Ptr *complex64
// // Complex128Ptr *complex128
// TestPtr *testObj
// TestArray []testObj
// }
type testObj struct {
String string `ld:"string" json:"string"`
NoTag string
Int int
Int8 int8
Int16 int16
Int32 int32
Int64 int64
Uint uint
Uint8 uint8
Uint16 uint16
Uint32 uint32
Uint64 uint64
Float32 float32
Float64 float64
// Complex64 complex64
// Complex128 complex128
IntPtr *int
Int8Ptr *int8
Int16Ptr *int16
Int32Ptr *int32
Int64Ptr *int64
UintPtr *uint
Uint8Ptr *uint8
Uint16Ptr *uint16
Uint32Ptr *uint32
Uint64Ptr *uint64
Float32Ptr *float32
Float64Ptr *float64
// Complex64Ptr *complex64
// Complex128Ptr *complex128
TestPtr *testObj
TestArray []testObj
}
// func TestUnmarshal(t *testing.T) {
// tests := map[string]struct {
// obj testObj
// errCheck epk.ErrorTest
// }{
// "string children": {
// obj: testObj{
// String: "hello",
// NoTag: "no_tag",
// Int: math.MaxInt,
// Int8: math.MaxInt8,
// Int16: math.MaxInt16,
// Int32: math.MaxInt32,
// Int64: math.MaxInt64,
// Uint: math.MaxUint,
// Uint8: math.MaxUint8,
// Uint16: math.MaxUint16,
// Uint32: math.MaxUint32,
// Uint64: math.MaxUint64,
// Float32: math.MaxFloat32,
// Float64: math.MaxFloat64,
// TestPtr: &testObj{
// String: "hello2",
// },
// TestArray: []testObj{
// {
// String: "hello3",
// },
// {
// String: "hello4",
// TestPtr: &testObj{
// TestArray: []testObj{
// {
// String: "hello5",
// },
// },
// },
// },
// },
// // Complex64: complex(math.MaxFloat32, math.MaxFloat32),
// // Complex128: complex(math.MaxFloat64, math.MaxFloat64),
// },
// },
// }
func TestUnmarshal(t *testing.T) {
tests := map[string]struct {
obj testObj
errCheck epk.ErrorTest
}{
"string children": {
obj: testObj{
String: "hello",
NoTag: "no_tag",
Int: math.MaxInt,
Int8: math.MaxInt8,
Int16: math.MaxInt16,
Int32: math.MaxInt32,
Int64: math.MaxInt64,
Uint: math.MaxUint,
Uint8: math.MaxUint8,
Uint16: math.MaxUint16,
Uint32: math.MaxUint32,
Uint64: math.MaxUint64,
Float32: math.MaxFloat32,
Float64: math.MaxFloat64,
TestPtr: &testObj{
String: "hello2",
},
TestArray: []testObj{
{
String: "hello3",
},
{
String: "hello4",
TestPtr: &testObj{
TestArray: []testObj{
{
String: "hello5",
},
},
},
},
},
// Complex64: complex(math.MaxFloat32, math.MaxFloat32),
// Complex128: complex(math.MaxFloat64, math.MaxFloat64),
},
},
}
// for name, test := range tests {
// test := test
// t.Run(name, func(tt *testing.T) {
// that := require.New(tt)
// objJSON, err := json.MarshalIndent(test.obj, "", " ")
// that.NoError(err)
// result, err := asld.Unmarshal[testObj](objJSON)
// that.NoError(err)
// that.Equal(test.obj, result)
// })
// }
// }
for name, test := range tests {
test := test
t.Run(name, func(tt *testing.T) {
that := require.New(tt)
objJSON, err := json.MarshalIndent(test.obj, "", " ")
that.NoError(err)
result, err := ld.Unmarshal[testObj](objJSON)
that.NoError(err)
that.Equal(test.obj, result)
})
}
}
// func TestBench(t *testing.T) {
// obj := testObj{
// String: "hello",
// NoTag: "no_tag",
// Int: math.MaxInt,
// Int8: math.MaxInt8,
// Int16: math.MaxInt16,
// Int32: math.MaxInt32,
// Int64: math.MaxInt64,
// Uint: math.MaxUint,
// Uint8: math.MaxUint8,
// Uint16: math.MaxUint16,
// Uint32: math.MaxUint32,
// Uint64: math.MaxUint64,
// Float32: math.MaxFloat32,
// Float64: math.MaxFloat64,
// // Complex64: complex(math.MaxFloat32, math.MaxFloat32),
// // Complex128: complex(math.MaxFloat64, math.MaxFloat64),
// }
// that := require.New(t)
// asldTotal := int64(0)
// jsonTotal := int64(0)
func TestBench(t *testing.T) {
obj := testObj{
String: "hello",
NoTag: "no_tag",
Int: math.MaxInt,
Int8: math.MaxInt8,
Int16: math.MaxInt16,
Int32: math.MaxInt32,
Int64: math.MaxInt64,
Uint: math.MaxUint,
Uint8: math.MaxUint8,
Uint16: math.MaxUint16,
Uint32: math.MaxUint32,
Uint64: math.MaxUint64,
Float32: math.MaxFloat32,
Float64: math.MaxFloat64,
// Complex64: complex(math.MaxFloat32, math.MaxFloat32),
// Complex128: complex(math.MaxFloat64, math.MaxFloat64),
}
that := require.New(t)
asldTotal := int64(0)
jsonTotal := int64(0)
// jsonMax := int64(0)
// jsonMin := math.MaxInt64
jsonMax := int64(0)
jsonMin := math.MaxInt64
// asldMax := int64(0)
// asldMin := math.MaxInt64
asldMax := int64(0)
asldMin := math.MaxInt64
// count := int64(1 << 20)
// for index := int64(0); index < count; index++ {
// objJSON, err := json.Marshal(obj)
// that.NoError(err)
count := int64(1 << 20)
for index := int64(0); index < count; index++ {
objJSON, err := json.Marshal(obj)
that.NoError(err)
// asldStart := time.Now()
// _, err = asld.Unmarshal[testObj](objJSON)
// asldDur := time.Since(asldStart)
// asldTotal += int64(asldDur)
// if asldDur < time.Duration(asldMin) {
// asldMin = int(asldDur)
// }
// if asldDur > time.Duration(asldMax) {
// asldMax = int64(asldDur)
// }
asldStart := time.Now()
_, err = ld.Unmarshal[testObj](objJSON)
asldDur := time.Since(asldStart)
asldTotal += int64(asldDur)
if asldDur < time.Duration(asldMin) {
asldMin = int(asldDur)
}
if asldDur > time.Duration(asldMax) {
asldMax = int64(asldDur)
}
// that.NoError(err)
// a := testObj{}
that.NoError(err)
a := testObj{}
// jsonStart := time.Now()
// err = json.Unmarshal(objJSON, &a)
// jsonDur := time.Since(jsonStart)
// jsonTotal += int64(jsonDur)
jsonStart := time.Now()
err = json.Unmarshal(objJSON, &a)
jsonDur := time.Since(jsonStart)
jsonTotal += int64(jsonDur)
// if jsonDur < time.Duration(jsonMin) {
// jsonMin = int(jsonDur)
// }
// if jsonDur > time.Duration(jsonMax) {
// jsonMax = int64(jsonDur)
// }
if jsonDur < time.Duration(jsonMin) {
jsonMin = int(jsonDur)
}
if jsonDur > time.Duration(jsonMax) {
jsonMax = int64(jsonDur)
}
// that.NoError(err)
// }
// fmt.Println(count, "runs")
// fmt.Printf("json avg (%s), min (%s), max (%s)\n", time.Duration(jsonTotal/count), time.Duration(jsonMin), time.Duration(jsonMax))
// fmt.Printf("asld avg (%s), min (%s), max (%s)\n", time.Duration(asldTotal/count), time.Duration(asldMin), time.Duration(asldMax))
// }
// func jsonElemList[T any](that *require.Assertions, a any) [][]byte {
// that.NotNil(a)
// v, ok := a.([]T)
// that.True(ok)
// fields := make([][]byte, len(v))
// for index, field := range v {
// jsonField, err := json.Marshal(field)
// that.NoError(err)
// fields[index] = jsonField
// }
// return fields
// }
that.NoError(err)
}
fmt.Println(count, "runs")
fmt.Printf("json avg (%s), min (%s), max (%s)\n", time.Duration(jsonTotal/count), time.Duration(jsonMin), time.Duration(jsonMax))
fmt.Printf("asld avg (%s), min (%s), max (%s)\n", time.Duration(asldTotal/count), time.Duration(asldMin), time.Duration(asldMax))
}
func jsonElemList[T any](that *require.Assertions, a any) [][]byte {
that.NotNil(a)
v, ok := a.([]T)
that.True(ok)
fields := make([][]byte, len(v))
for index, field := range v {
jsonField, err := json.Marshal(field)
that.NoError(err)
fields[index] = jsonField
}
return fields
}
// type hello struct {
// Hello string
// }
type hello struct {
Hello string
}
// func TestArrayMembers(t *testing.T) {
// tests := map[string]struct {