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]) out[index] = f(c[index])
} }
return From(out) return From(out...)
} }
func Filter[T any](v Vector[T], f func(T) bool) Vector[T] { 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 { if len(v) < howMany {
howMany = len(v) 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 { 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 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 { type numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 ~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) 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) 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] { 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 // 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) v.panicIndex(end)
} }
if start < 0 { if start < 0 {
return From(v[:end]) return From(v[:end]...)
} }
if end < 0 { 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] { 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) { "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.Equal(6, len(v))
that.EqualValues([]int{1, 2, 3, 4, 5, 6}, v) that.EqualValues([]int{1, 2, 3, 4, 5, 6}, v)
}, },
"getsoft": func(that *require.Assertions) { "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.Equal(2, v.GetSoft(1))
that.Zero(v.GetSoft(-1000)) that.Zero(v.GetSoft(-1000))
that.Zero(v.GetSoft(1000)) that.Zero(v.GetSoft(1000))
}, },
"get": func(that *require.Assertions) { "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.Equal(2, v.Get(1))
that.Panics(func() { that.Panics(func() {
v.Get(-1000) v.Get(-1000)
@ -80,7 +80,7 @@ func TestVector(t *testing.T) {
}) })
}, },
"clone": func(that *require.Assertions) { "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) v1 := v.Clone().Set(1, 1)
that.Equal(3, len(v1)) that.Equal(3, len(v1))
@ -88,7 +88,7 @@ func TestVector(t *testing.T) {
that.EqualValues([]int{1, 2, 3}, v) that.EqualValues([]int{1, 2, 3}, v)
}, },
"set": func(that *require.Assertions) { "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) v1 := v.Clone().Set(1, 1)
that.Equal(3, len(v1)) that.Equal(3, len(v1))
@ -110,7 +110,7 @@ func TestVector(t *testing.T) {
}) })
}, },
"sub": func(that *require.Assertions) { "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) v1 := v.Clone().Sub(2, 4)
that.Equal(2, len(v1)) that.Equal(2, len(v1))
@ -139,13 +139,13 @@ func TestVector(t *testing.T) {
}) })
}, },
"filter": func(that *require.Assertions) { "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 }) v = v.Filter(func(i int) bool { return i%2 == 0 })
that.Len(v, 3) that.Len(v, 3)
}, },
"any": func(that *require.Assertions) { "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.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.False(coll.From([]int{1, 2, 3, 4, 5, 6}...).Any(func(i int) bool { return i == 666 }))
}, },
"take": func(that *require.Assertions) { "take": func(that *require.Assertions) {
that.EqualValues([]int{1, 2, 3}, coll.From([]int{1, 2, 3, 4, 5, 6}).Take(3)) 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 ( import (
"errors" "errors"
"fmt" "fmt"
"unicode"
"unicode/utf8"
"sectorinf.com/emilis/flabk/pkg/coll" "sectorinf.com/emilis/flabk/pkg/coll"
) )
const (
escapeChar = '\\'
unicodeEscape = 'u'
)
var ( var (
ErrNoMatch = errors.New("no matching") ErrNoMatch = errors.New("no matching")
ErrMapMemberNotString = errors.New("map member not a string") ErrMapMemberNotString = errors.New("map member not a string")
ErrUnexpectedSymbol = errors.New("unexpected symbol") 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 { type Chunk struct {
@ -24,72 +30,120 @@ type Chunk struct {
globRight int 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 // Match finds the matching closer to the symbol at the left position and sets the
// right position to this index // right position to this index
func (c Chunk) Match() (Chunk, error) { func (c Chunk) Match() (Chunk, error) {
s, ok := matchers[c.vector[c.posLeft]] start := c.vector[c.posLeft]
matcher, ok := matchers[start]
if !ok { if !ok {
panic(fmt.Sprintf("Match called on %c with no matcher defined", c.vector[c.posLeft])) 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
}
c.posRight--
c.globRight--
}
} else {
for index := c.posLeft + 1; index < len(c.vector); index++ { for index := c.posLeft + 1; index < len(c.vector); index++ {
if c.vector[index] == s.MatchByte { if start != '"' && c.vector[index] == start {
c.posRight = index sub, err := c.Child(index, len(c.vector)).Match()
c.globRight += (c.posLeft + 1) - index if err != nil {
return c, nil return c, fmt.Errorf("[%d] child %w", c.globLeft+(c.posLeft-index), err)
} }
index += sub.posRight
continue
}
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 { func (c Chunk) Copy() Chunk {
return c return c
} }
func (c Chunk) Left() byte {
return c.vector[c.posLeft]
}
// Sub returns an inclusive subchunk of [left;right] // Sub returns an inclusive subchunk of [left;right]
func (c Chunk) Sub() Chunk { 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) // CookieCutter returns a subchunk of (left;right)
func (c Chunk) CookieCutter() Chunk { 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 { func (c Chunk) AtSpace() bool {
r, _ := utf8.DecodeRune(c.vector[c.posLeft:]) return spaceAtIndex(c.vector, c.posLeft)
return unicode.IsSpace(r)
} }
func (c Chunk) Seek() Chunk { func (c Chunk) Seek() Chunk {
for c.posLeft < len(c.vector) && c.AtSpace() { for c.posLeft < len(c.vector) && c.AtSpace() {
c.globLeft++
c.posLeft++ c.posLeft++
} }
return c 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) { func (c Chunk) ValueEnd() (Chunk, error) {
switch c.vector[c.posLeft] { switch c.vector[c.posLeft] {
case '"', '{', '[': case '"', '{', '[':
return c.Match() return c.Match()
default: default:
for index := c.posLeft; index <= c.posRight; index++ { for index := c.posLeft; index <= c.posRight; index++ {
if c.vector[index] == ',' { if c.vector[index] == ',' || spaceAtIndex(c.vector, index) {
return New(c.vector[c.posLeft:index]), nil if index == c.posLeft {
return c, c.LeftPosf("%w", ErrMissingValue)
}
return c.Child(c.posLeft, index), nil
} }
} }
return c, nil return c, nil
@ -100,6 +154,7 @@ func (c Chunk) ValueEnd() (Chunk, error) {
func (c Chunk) Skip() Chunk { func (c Chunk) Skip() Chunk {
if c.posLeft+1 < len(c.vector) { if c.posLeft+1 < len(c.vector) {
c.posLeft++ c.posLeft++
c.globLeft++
} }
return c.Seek() return c.Seek()
} }
@ -110,18 +165,10 @@ type MatchRule struct {
} }
var ( var (
matchers = map[byte]MatchRule{ matchers = map[byte]byte{
'{': { '{': '}',
MatchByte: '}', '[': ']',
StartFromRight: true, '"': '"',
},
'[': {
MatchByte: ']',
StartFromRight: true,
},
'"': {
MatchByte: '"',
},
} }
) )
@ -134,6 +181,7 @@ func New(v []byte) Chunk {
vector: coll.Vector[byte](v), vector: coll.Vector[byte](v),
posLeft: 0, posLeft: 0,
posRight: posRight, posRight: posRight,
globRight: posRight,
} }
} }
@ -142,31 +190,14 @@ func (c Chunk) Child(left, right int) Chunk {
return Chunk{ return Chunk{
vector: sub, vector: sub,
posLeft: 0, posLeft: 0,
posRight: len(sub), posRight: len(sub) - 1,
globLeft: (c.globLeft - c.posLeft) + left, 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) 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 { type Row struct {
Name string Name string
Value Chunk Value Chunk
@ -177,47 +208,47 @@ func (c Chunk) String() string {
} }
func (c Chunk) Row() (Row, error) { func (c Chunk) Row() (Row, error) {
c = c.Seek()
if c.vector[c.posLeft] != '"' { 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() name, err := c.Match()
if err != nil { if err != nil {
return Row{}, fmt.Errorf("match: %w", err) return Row{}, c.LeftPosf("match: %w", err)
} }
postName := c.Copy() postName := c.Copy()
postName.posLeft = name.posRight postName.posLeft = name.posRight
postName.globLeft = name.globRight
postName = postName.Skip() postName = postName.Skip()
// Next we must get a : // Next we must get a :
if postName.vector[postName.posLeft] != ':' { 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() value, err := postName.Skip().ValueEnd()
if err != nil { if err != nil {
return Row{}, fmt.Errorf("value: %w", err) return Row{}, postName.LeftPosf("value: %w", err)
} }
return Row{ return Row{
Name: name.String(), Name: name.String()[1 : name.posRight-name.posLeft],
Value: value, Value: value,
}, nil }, 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 { 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 return c
} }
func ParseMap[T any](c Chunk) ParseFunc[T] { func (c Chunk) EOF() bool {
return func(t T) (T, error) { return c.posLeft >= len(c.vector)-1
// mapper := parse.GetMap(t)
// for {
// row, err := c.Row()
// if err != nil {
// return t, err
// }
// }
panic("todo")
}
} }

View File

@ -1,37 +1,108 @@
package chunk_test package chunk
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "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) { func TestSpace(t *testing.T) {
tests := []byte{0x9, 0xa, 0xd, 0x20}
that := require.New(t) that := require.New(t)
ch := chunk.New([]byte(" hello world")) for _, test := range tests {
that.True(ch.AtSpace()) b := []byte("xhello world")
b[0] = test
that.True(New(b).AtSpace())
}
} }
func TestParseMap(t *testing.T) { func TestAlign(t *testing.T) {
type Hello struct { tests := map[string]struct {
Hello string c func() Chunk
} v func(Chunk) Chunk
ch := chunk.New([]byte(`"Hello": "world"`)) }{
h, err := chunk.ParseMap[Hello](ch)(Hello{}) "same-size": {
if err != nil { c: func() Chunk {
panic(err) c := New(coll.Vector[byte](`{"heres some test data": "hi mom"}`))
} c.posLeft = 15
fmt.Println(h) 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)
},
},
} }
func TestChild(t *testing.T) { for name, test := range tests {
type Hello struct { test := test
Hello string 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()))
})
}
} }
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 package parse
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"sectorinf.com/emilis/flabk/pkg/coll"
"sectorinf.com/emilis/flabk/pkg/ld/internal/consts" "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 { var (
val := reflect.ValueOf(v) ErrNotArray = errors.New("value is not an array")
// typ := reflect.TypeOf(v) ErrNotMap = errors.New("value is not a map")
switch val.Kind() { )
case reflect.Map:
return func(name string, value string) { var (
// val.SetMapIndex(reflect.ValueOf(name), value) boolVals = map[string]bool{
"true": true,
"false": false,
} }
case reflect.Struct: determineMap = func() map[byte]coll.Vector[reflect.Kind] {
// fields := GetStructFields(val, typ) m := map[byte]coll.Vector[reflect.Kind]{
return func(name, value string) { '"': coll.From(reflect.String),
// val, ok := fields[name] '[': coll.From(reflect.Array, reflect.Slice),
// if ok { '{': coll.From(reflect.Map, reflect.Struct),
// val.Set(value) '-': coll.From(reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64),
// } 't': coll.From(reflect.Bool),
} 'f': coll.From(reflect.Bool),
default:
panic("wrong")
} }
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
} }
func ParseAsValue(v string, valType reflect.Type) (any, error) { return m
// Might not be necessary to trim }()
v = strings.TrimSpace(v) sizes = map[reflect.Kind]int{
switch valType.Kind() { // Int and Uint values stolen from the [math] package, as the intSize
case reflect.String: // constant is unexported
return v[1 : len(v)-1], nil reflect.Int: 32 << (^uint(0) >> 63), // 32 or 64
case reflect.Bool: reflect.Uint: 32 << (^uint(0) >> 63), // 32 or 64
b, err := strconv.ParseBool(strings.ToLower(v)) 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 { if err != nil {
fmt.Errorf("boolean: %w", err) return fmt.Errorf("row: %w", err)
} }
return b, nil field, ok := fields[row.Name]
case reflect.Struct, reflect.Map: if ok {
panic("todo") 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 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: default:
panic("todo") 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 { func GetStructFields(val reflect.Value, typ reflect.Type) map[string]reflect.Value {

View File

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