flabk/pkg/asld/unmarshal.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)
}