diff --git a/pkg/coll/coll.go b/pkg/coll/coll.go index 7b95c46..3be85ea 100644 --- a/pkg/coll/coll.go +++ b/pkg/coll/coll.go @@ -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 } diff --git a/pkg/coll/vector.go b/pkg/coll/vector.go index 74123df..02d9652 100644 --- a/pkg/coll/vector.go +++ b/pkg/coll/vector.go @@ -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] { diff --git a/pkg/coll/vector_test.go b/pkg/coll/vector_test.go index b5c46f8..723b37a 100644 --- a/pkg/coll/vector_test.go +++ b/pkg/coll/vector_test.go @@ -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)) diff --git a/pkg/ld/internal/parse/chunk/chunk.go b/pkg/ld/internal/parse/chunk/chunk.go index f65a921..ef615dd 100644 --- a/pkg/ld/internal/parse/chunk/chunk.go +++ b/pkg/ld/internal/parse/chunk/chunk.go @@ -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 } diff --git a/pkg/ld/internal/parse/chunk/chunk_test.go b/pkg/ld/internal/parse/chunk/chunk_test.go index 7b8ed0d..01a89fc 100644 --- a/pkg/ld/internal/parse/chunk/chunk_test.go +++ b/pkg/ld/internal/parse/chunk/chunk_test.go @@ -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()) } diff --git a/pkg/ld/internal/parse/parse.go b/pkg/ld/internal/parse/parse.go index 71d313e..f9c4d6a 100644 --- a/pkg/ld/internal/parse/parse.go +++ b/pkg/ld/internal/parse/parse.go @@ -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 { diff --git a/pkg/ld/unmarshal.go b/pkg/ld/unmarshal.go index 7330ae2..bc097d9 100644 --- a/pkg/ld/unmarshal.go +++ b/pkg/ld/unmarshal.go @@ -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 +} diff --git a/pkg/ld/unmarshal_test.go b/pkg/ld/unmarshal_test.go index 151d9d8..3c53000 100644 --- a/pkg/ld/unmarshal_test.go +++ b/pkg/ld/unmarshal_test.go @@ -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 {