300 lines
7.4 KiB
Go
300 lines
7.4 KiB
Go
|
package asld
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
intSize = 32 << (^uint(0) >> 63) // unexported from "math"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
bitSize = map[reflect.Kind]int{
|
||
|
reflect.Int: intSize,
|
||
|
reflect.Int8: 8,
|
||
|
reflect.Int16: 16,
|
||
|
reflect.Int32: 32,
|
||
|
reflect.Int64: 64,
|
||
|
reflect.Uint: intSize,
|
||
|
reflect.Uint8: 8,
|
||
|
reflect.Uint16: 16,
|
||
|
reflect.Uint32: 32,
|
||
|
reflect.Uint64: 64,
|
||
|
reflect.Float32: 32,
|
||
|
reflect.Float64: 64,
|
||
|
reflect.Complex64: 64,
|
||
|
reflect.Complex128: 128,
|
||
|
}
|
||
|
nullWalker = newWalker([]byte{'n', 'u', 'l', 'l'})
|
||
|
)
|
||
|
|
||
|
func arrayMembers(w walker) ([]walker, error) {
|
||
|
var (
|
||
|
members = []walker{}
|
||
|
final bool
|
||
|
)
|
||
|
|
||
|
elements, ok := w.SliceInner()
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("%s %w", string(w.content[w.position]), ErrNoMatching)
|
||
|
}
|
||
|
|
||
|
for !final {
|
||
|
elem, err := elements.CommaOrEnd()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("comma or end %s: %w", string(elem.content), err)
|
||
|
}
|
||
|
final = elem.status == statusWalkerNotAffected
|
||
|
value := elem.walker.Until().Reset()
|
||
|
if !final {
|
||
|
// Incremenet elem.walker.position here so
|
||
|
// that it skips over the comma that we're
|
||
|
// on right now. as there's not really anything
|
||
|
// valid to stop on in arrays aside from them
|
||
|
// so we can't just call ToNext() later.
|
||
|
elem.walker = elem.walker.Pos(elem.walker.position + 1)
|
||
|
} else {
|
||
|
value = elem.walker
|
||
|
}
|
||
|
elements = elem.walker.Sub()
|
||
|
members = append(members, value)
|
||
|
}
|
||
|
|
||
|
return members, nil
|
||
|
}
|
||
|
|
||
|
func mapMembers(w walker) (map[string]walker, error) {
|
||
|
var (
|
||
|
members = map[string]walker{}
|
||
|
lastLine bool
|
||
|
//debug
|
||
|
prev string
|
||
|
)
|
||
|
w, sym := w.StayOrNext()
|
||
|
if sym == symbolEOB {
|
||
|
w, sym = w.StayOrNext()
|
||
|
panic("idk")
|
||
|
}
|
||
|
// Because this gets called recursively with all kinds of
|
||
|
// inputs and spacing, we should do a Stay/Walk If Space situation
|
||
|
elements, ok := w.SliceInner()
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("%s %w", string(w.content[w.position]), ErrNoMatching)
|
||
|
}
|
||
|
|
||
|
w.Debug()
|
||
|
for !lastLine {
|
||
|
lineInfo, err := elements.CommaOrEnd()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("comma or end: %s: %w", string(elements.content), err)
|
||
|
}
|
||
|
lastLine = lineInfo.status == statusWalkerNotAffected
|
||
|
line := lineInfo.walker
|
||
|
if !lastLine {
|
||
|
line = line.Until()
|
||
|
}
|
||
|
|
||
|
name, ok := line.Reset().ToOrStay('"')
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
nameString, ok := name.SliceInner()
|
||
|
if !ok {
|
||
|
// TODO: maybe these should have global position
|
||
|
return nil, fmt.Errorf("%s %w", string(name.Current()), ErrNoMatching)
|
||
|
}
|
||
|
|
||
|
// We know this is OK because the above SliceInner called it
|
||
|
name, _ = name.To('"')
|
||
|
|
||
|
wNext, sym := name.Next()
|
||
|
if sym != symbolEOB && wNext.Current() != ':' && isIRI(nameString.content) {
|
||
|
panic("IRI expansion not implemented")
|
||
|
} else if sym != symbolEOB && wNext.Current() != ':' {
|
||
|
return nil, fmt.Errorf("%s at pos %d: %w", string(nameString.content), wNext.globPos, ErrEntryWithoutValue)
|
||
|
}
|
||
|
|
||
|
value, sym := wNext.Next()
|
||
|
if sym == symbolEOB {
|
||
|
if value.position < value.len-1 {
|
||
|
value = value.Pos(value.position + 1).Sub()
|
||
|
} else {
|
||
|
return nil, fmt.Errorf("non-IRI %s: %w", string(nameString.content), ErrEntryWithoutValue)
|
||
|
}
|
||
|
} else if sym == symbolNullStart {
|
||
|
value = nullWalker
|
||
|
}
|
||
|
// Walk to next viable token
|
||
|
value = value.WalkThroughSpaces().Sub()
|
||
|
elements = lineInfo.walker.Sub()
|
||
|
n := string(nameString.content)
|
||
|
print(n + "\n")
|
||
|
fmt.Println(value)
|
||
|
members[n] = value
|
||
|
prev = n
|
||
|
}
|
||
|
|
||
|
fmt.Print(prev)
|
||
|
return members, nil
|
||
|
}
|
||
|
|
||
|
func unmarshalStruct(out reflect.Value, w walker) error {
|
||
|
members, err := mapMembers(w)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting members: %w", err)
|
||
|
}
|
||
|
|
||
|
outType := out.Type()
|
||
|
// Deconstruct the struct fields
|
||
|
for index := 0; index < out.NumField(); index++ {
|
||
|
field := out.Field(index)
|
||
|
fType := outType.Field(index)
|
||
|
|
||
|
tagInfo := fType.Tag.Get(tagName)
|
||
|
// TODO: support expandible/collapsible/whatever I name it
|
||
|
// and omitempty probably
|
||
|
tagParts := strings.Split(tagInfo, ",")
|
||
|
name := tagParts[0]
|
||
|
if tagInfo == "" || name == "" {
|
||
|
name = fType.Name
|
||
|
}
|
||
|
// mimic encoding/json behavior
|
||
|
if name == "-" && len(tagParts) == 1 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
wField, exists := members[name]
|
||
|
if !exists {
|
||
|
continue
|
||
|
}
|
||
|
fmt.Println(wField, field)
|
||
|
// if err := setValue(field, wField, fType); err != nil {
|
||
|
// return fmt.Errorf("field %s set: %w", name, err)
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func setMap(field reflect.Value, w walker) error {
|
||
|
if field.IsNil() {
|
||
|
field.Set(reflect.MakeMap(field.Type()))
|
||
|
}
|
||
|
keyType := field.Type().Key()
|
||
|
if keyType.Kind() != reflect.String {
|
||
|
return ErrMapNotStringIndexed
|
||
|
}
|
||
|
members, err := mapMembers(w)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting members for map: %w", err)
|
||
|
}
|
||
|
|
||
|
valueType := field.Type().Elem()
|
||
|
for key, member := range members {
|
||
|
def := reflect.Indirect(reflect.New(valueType))
|
||
|
err = setValue(def, member)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf(
|
||
|
"could not set %s to %s: %w",
|
||
|
string(w.content),
|
||
|
valueType.Kind().String(),
|
||
|
err,
|
||
|
)
|
||
|
}
|
||
|
field.SetMapIndex(reflect.ValueOf(key), def)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func setValue(field reflect.Value, w walker, a ...reflect.StructField) error {
|
||
|
var (
|
||
|
err error
|
||
|
setter func()
|
||
|
)
|
||
|
if w.len >= 4 && bytes.Equal(null, w.content[:4]) {
|
||
|
// default value
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
k := field.Kind()
|
||
|
switch k {
|
||
|
case reflect.String:
|
||
|
if w.content[0] != '"' {
|
||
|
err = fmt.Errorf("%s is not a string", string(w.content))
|
||
|
}
|
||
|
setter = func() { field.SetString(w.String()) }
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
|
var i int64
|
||
|
i, err = strconv.ParseInt(string(w.content), 10, bitSize[field.Kind()])
|
||
|
setter = func() { field.SetInt(i) }
|
||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
|
var i uint64
|
||
|
i, err = strconv.ParseUint(string(w.content), 10, bitSize[field.Kind()])
|
||
|
setter = func() { field.SetUint(i) }
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
var f float64
|
||
|
f, err = strconv.ParseFloat(string(w.content), bitSize[field.Kind()])
|
||
|
setter = func() { field.SetFloat(f) }
|
||
|
case reflect.Complex64, reflect.Complex128:
|
||
|
var c complex128
|
||
|
c, err = strconv.ParseComplex(string(w.content), bitSize[field.Kind()])
|
||
|
setter = func() { field.SetComplex(c) }
|
||
|
case reflect.Pointer:
|
||
|
if field.IsNil() {
|
||
|
field.Set(reflect.New(field.Type().Elem()))
|
||
|
}
|
||
|
// prob oops if its nil
|
||
|
fDeref := reflect.Indirect(field)
|
||
|
return setValue(fDeref, w)
|
||
|
case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
|
||
|
if k == reflect.Array || k == reflect.Slice && len(a) > 0 {
|
||
|
fmt.Println("$$$$$$$$$$$", a[0].Name)
|
||
|
}
|
||
|
return unmarshal(field, w)
|
||
|
case reflect.Bool:
|
||
|
var b bool
|
||
|
b, err = strconv.ParseBool(string(w.content))
|
||
|
setter = func() { field.SetBool(b) }
|
||
|
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||
|
// Ignore
|
||
|
return nil
|
||
|
default:
|
||
|
panic("not implemented")
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("could not parse %s as %s: %w", string(w.content), field.Kind().String(), err)
|
||
|
}
|
||
|
setter()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func unmarshal(out reflect.Value, w walker) error {
|
||
|
switch out.Kind() {
|
||
|
case reflect.Struct:
|
||
|
return unmarshalStruct(out, w)
|
||
|
case reflect.Map:
|
||
|
return setMap(out, w)
|
||
|
case reflect.Array, reflect.Slice:
|
||
|
// do array stuff here
|
||
|
panic("todo")
|
||
|
default:
|
||
|
panic(out.Kind().String() + " not yet supported")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Unmarshal[T any](data []byte) (T, error) {
|
||
|
tPtr := new(T)
|
||
|
tValue := reflect.Indirect(reflect.ValueOf(tPtr))
|
||
|
w := newWalker(data)
|
||
|
|
||
|
// Might be array/map/interface at top level, should check
|
||
|
return *tPtr, unmarshal(tValue, w)
|
||
|
}
|