flabk/pkg/ld/internal/parse/parse.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
}