220 lines
5.3 KiB
Go
220 lines
5.3 KiB
Go
package parse
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"sectorinf.com/emilis/flabk/pkg/coll"
|
|
"sectorinf.com/emilis/flabk/pkg/ld/internal/consts"
|
|
"sectorinf.com/emilis/flabk/pkg/ld/internal/parse/chunk"
|
|
)
|
|
|
|
const (
|
|
memberSeparator = ','
|
|
)
|
|
|
|
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 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 {
|
|
total := val.NumField()
|
|
out := map[string]reflect.Value{}
|
|
for index := 0; index < total; index++ {
|
|
cName := StructName(typ.Field(index))
|
|
if cName != "-" {
|
|
out[cName] = val.Field(index)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func StructName(v reflect.StructField) string {
|
|
tag := v.Tag.Get(consts.PkgTag)
|
|
if tag != "" {
|
|
return tag
|
|
}
|
|
// Default to field name
|
|
return v.Name
|
|
}
|