Fin/data.go

605 lines
12 KiB
Go

package fin
import (
"fmt"
"io"
"maps"
"os"
"slices"
"strconv"
)
type Data struct {
data map[string]any
dataRef *map[string]any
scanner *Scanner
currentObject map[string]any
stack []map[string]any
currentToken Token
lastError error
}
func NewData() *Data {
dataRef := GetMap()
data := *dataRef
d := &Data{
data: data,
dataRef: dataRef,
stack: make([]map[string]any, 0, 8),
}
d.currentObject = d.data
return d
}
func (d *Data) Release() {
if d.scanner != nil {
ReleaseScanner(d.scanner)
d.scanner = nil
}
if d.dataRef != nil {
PutMap(d.dataRef)
d.data = nil
d.dataRef = nil
}
d.currentObject = nil
d.stack = nil
}
func (d *Data) GetData() map[string]any { return d.data }
func (d *Data) Get(key string) (any, error) {
if key == "" {
return d.data, nil
}
var current any = d.data
start := 0
keyLen := len(key)
for i := range keyLen {
if key[i] == '.' || i == keyLen-1 {
end := i
if i == keyLen-1 && key[i] != '.' {
end = i + 1
}
part := key[start:end]
switch node := current.(type) {
case map[string]any:
val, ok := node[part]
if !ok {
return nil, fmt.Errorf("key %s not found", part)
}
current = val
case []any:
index, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("invalid array index: %s", part)
}
if index < 0 || index >= len(node) {
return nil, fmt.Errorf("array index out of bounds: %d", index)
}
current = node[index]
case []string:
index, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("invalid array index: %s", part)
}
if index < 0 || index >= len(node) {
return nil, fmt.Errorf("array index out of bounds: %d", index)
}
current = node[index]
case []int:
index, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("invalid array index: %s", part)
}
if index < 0 || index >= len(node) {
return nil, fmt.Errorf("array index out of bounds: %d", index)
}
current = node[index]
default:
return nil, fmt.Errorf("cannot access %s in non-container value", part)
}
if i == keyLen-1 {
return current, nil
}
start = i + 1
}
}
return current, nil
}
func (d *Data) GetOr(key string, defaultValue any) any {
val, err := d.Get(key)
if err != nil {
return defaultValue
}
return val
}
func convertTo[T any](val any, key string, converter func(any) (T, bool)) (T, error) {
var zero T
result, ok := converter(val)
if ok {
return result, nil
}
return zero, fmt.Errorf("value for key %s cannot be converted to %T", key, zero)
}
func toString(val any) (string, bool) {
switch v := val.(type) {
case string:
return v, true
case bool:
return strconv.FormatBool(v), true
case int:
return strconv.Itoa(v), true
case float64:
return strconv.FormatFloat(v, 'f', -1, 64), true
}
return "", false
}
func toBool(val any) (bool, bool) {
switch v := val.(type) {
case bool:
return v, true
case string:
if result, err := strconv.ParseBool(v); err == nil {
return result, true
}
}
return false, false
}
func toInt(val any) (int, bool) {
switch v := val.(type) {
case int:
return v, true
case float64:
return int(v), true
case string:
if result, err := strconv.Atoi(v); err == nil {
return result, true
}
}
return 0, false
}
func toFloat(val any) (float64, bool) {
switch v := val.(type) {
case float64:
return v, true
case int:
return float64(v), true
case string:
if result, err := strconv.ParseFloat(v, 64); err == nil {
return result, true
}
}
return 0, false
}
func (d *Data) GetString(key string) (string, error) {
val, err := d.Get(key)
if err != nil {
return "", err
}
return convertTo(val, key, toString)
}
func (d *Data) GetBool(key string) (bool, error) {
val, err := d.Get(key)
if err != nil {
return false, err
}
return convertTo(val, key, toBool)
}
func (d *Data) GetInt(key string) (int, error) {
val, err := d.Get(key)
if err != nil {
return 0, err
}
return convertTo(val, key, toInt)
}
func (d *Data) GetFloat(key string) (float64, error) {
val, err := d.Get(key)
if err != nil {
return 0, err
}
return convertTo(val, key, toFloat)
}
func (d *Data) GetArray(key string) ([]any, error) {
val, err := d.Get(key)
if err != nil {
return nil, err
}
switch v := val.(type) {
case []any:
return v, nil
case []string:
result := make([]any, len(v))
for i, s := range v {
result[i] = s
}
return result, nil
case []int:
result := make([]any, len(v))
for i, n := range v {
result[i] = n
}
return result, nil
default:
return nil, fmt.Errorf("value for key %s is not an array", key)
}
}
func (d *Data) GetMap(key string) (map[string]any, error) {
val, err := d.Get(key)
if err != nil {
return nil, err
}
if m, ok := val.(map[string]any); ok {
return m, nil
}
return nil, fmt.Errorf("value for key %s is not a map", key)
}
func getTypedArray[T any](d *Data, key string, converter func(any) (T, bool)) ([]T, error) {
arr, err := d.GetArray(key)
if err != nil {
return nil, err
}
result := make([]T, len(arr))
for i, v := range arr {
converted, ok := converter(v)
if !ok {
return nil, fmt.Errorf("array element at index %d cannot be converted", i)
}
result[i] = converted
}
return result, nil
}
func (d *Data) GetStringArray(key string) ([]string, error) {
return getTypedArray(d, key, func(v any) (string, bool) {
s, ok := v.(string)
return s, ok
})
}
func (d *Data) GetIntArray(key string) ([]int, error) {
return getTypedArray(d, key, func(v any) (int, bool) {
switch val := v.(type) {
case int:
return val, true
case float64:
return int(val), true
}
return 0, false
})
}
func (d *Data) GetFloatArray(key string) ([]float64, error) {
return getTypedArray(d, key, func(v any) (float64, bool) {
switch val := v.(type) {
case float64:
return val, true
case int:
return float64(val), true
}
return 0, false
})
}
func (d *Data) Error(msg string) error {
return fmt.Errorf("line %d, column %d: %s", d.currentToken.Line, d.currentToken.Column, msg)
}
func (d *Data) makeStringKey(tokenValue []byte) string {
keyBytes := GetByteSlice()
*keyBytes = append((*keyBytes)[:0], tokenValue...)
result := string(*keyBytes)
PutByteSlice(keyBytes)
return result
}
func (d *Data) Parse(r io.Reader) error {
d.scanner = NewScanner(r)
d.currentObject = d.data
err := d.parseContent()
if d.scanner != nil {
ReleaseScanner(d.scanner)
d.scanner = nil
}
return err
}
func (d *Data) nextToken() (Token, error) {
for {
token, err := d.scanner.NextToken()
if err != nil {
return token, err
}
if token.Type != TokenComment {
d.currentToken = token
return token, nil
}
}
}
func (d *Data) parseContent() error {
for {
token, err := d.nextToken()
if err != nil {
return err
}
if token.Type == TokenEOF {
break
}
if token.Type != TokenName {
return d.Error(fmt.Sprintf("expected name at top level, got token type %v", token.Type))
}
name := d.makeStringKey(token.Value)
nextToken, err := d.nextToken()
if err != nil {
if err == io.EOF {
d.currentObject[name] = ""
break
}
return err
}
var value any
if nextToken.Type == TokenOpenBrace {
value, err = d.parseObject()
if err != nil {
return err
}
} else {
value = d.tokenToValue(nextToken)
lookAhead, nextErr := d.nextToken()
if nextErr == nil && lookAhead.Type == TokenOpenBrace {
nestedValue, err := d.parseObject()
if err != nil {
return err
}
if mapValue, ok := nestedValue.(map[string]any); ok {
mapRef := GetMap()
newMap := *mapRef
maps.Copy(newMap, mapValue)
newMap["value"] = value
value = newMap
}
} else if nextErr == nil && lookAhead.Type != TokenEOF {
d.scanner.UnreadToken(lookAhead)
}
}
d.currentObject[name] = value
}
return nil
}
func (d *Data) parseObject() (any, error) {
isArray := true
arrayRef := GetArray()
arrayElements := *arrayRef
mapRef := GetMap()
objectElements := *mapRef
defer func() {
if !isArray {
PutArray(arrayRef)
} else {
PutMap(mapRef)
}
}()
for {
token, err := d.nextToken()
if err != nil {
if err == io.EOF {
return nil, fmt.Errorf("unexpected EOF in object/array")
}
return nil, err
}
if token.Type == TokenCloseBrace {
if isArray && len(arrayElements) > 0 {
result := optimizeArray(arrayElements)
*arrayRef = nil
return result, nil
}
result := objectElements
*mapRef = nil
return result, nil
}
switch token.Type {
case TokenName:
key := d.makeStringKey(token.Value)
nextToken, err := d.nextToken()
if err != nil {
if err == io.EOF {
objectElements[key] = ""
isArray = false
return objectElements, nil
}
return nil, err
}
if nextToken.Type == TokenOpenBrace {
isArray = false
nestedValue, err := d.parseObject()
if err != nil {
return nil, err
}
objectElements[key] = nestedValue
} else {
isArray = false
value := d.tokenToValue(nextToken)
objectElements[key] = value
lookAhead, nextErr := d.nextToken()
if nextErr == nil && lookAhead.Type == TokenOpenBrace {
nestedValue, err := d.parseObject()
if err != nil {
return nil, err
}
if mapValue, ok := nestedValue.(map[string]any); ok {
combinedMapRef := GetMap()
combinedMap := *combinedMapRef
maps.Copy(combinedMap, mapValue)
combinedMap["value"] = value
objectElements[key] = combinedMap
}
} else if nextErr == nil && lookAhead.Type != TokenEOF && lookAhead.Type != TokenCloseBrace {
d.scanner.UnreadToken(lookAhead)
} else if nextErr == nil && lookAhead.Type == TokenCloseBrace {
d.scanner.UnreadToken(lookAhead)
}
}
case TokenString, TokenNumber, TokenBoolean:
value := d.tokenToValue(token)
arrayElements = append(arrayElements, value)
case TokenOpenBrace:
nestedValue, err := d.parseObject()
if err != nil {
return nil, err
}
if isArray {
arrayElements = append(arrayElements, nestedValue)
} else {
return nil, d.Error("unexpected nested object without a key")
}
default:
return nil, d.Error(fmt.Sprintf("unexpected token type: %v", token.Type))
}
}
}
func Load(r io.Reader) *Data {
data := NewData()
err := data.Parse(r)
if err != nil {
data.Release()
emptyData := NewData()
emptyData.lastError = err
return emptyData
}
return data
}
func LoadFromFile(filepath string) *Data {
file, err := os.Open(filepath)
if err != nil {
emptyData := NewData()
emptyData.lastError = err
return emptyData
}
defer file.Close()
return Load(file)
}
func (d *Data) HasError() bool {
return d.lastError != nil
}
func (d *Data) GetError() error {
return d.lastError
}
func (d *Data) tokenToValue(token Token) any {
switch token.Type {
case TokenString:
return d.makeStringKey(token.Value)
case TokenNumber:
valueStr := string(token.Value)
if slices.Contains(token.Value, '.') {
val, _ := strconv.ParseFloat(valueStr, 64)
return val
}
val, _ := strconv.Atoi(valueStr)
return val
case TokenBoolean:
return bytesEqual(token.Value, []byte("true"))
case TokenName:
if bytesEqual(token.Value, []byte("true")) || bytesEqual(token.Value, []byte("false")) {
return bytesEqual(token.Value, []byte("true"))
}
if len(token.Value) > 0 && isDigitOrMinus(token.Value[0]) {
valueStr := string(token.Value)
if slices.Contains(token.Value, '.') {
if val, err := strconv.ParseFloat(valueStr, 64); err == nil {
return val
}
} else {
if val, err := strconv.Atoi(valueStr); err == nil {
return val
}
}
return valueStr
}
return string(token.Value)
}
return nil
}
func isLetter(b byte) bool { return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') }
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func isDigitOrMinus(b byte) bool { return isDigit(b) || b == '-' }
func bytesEqual(b1, b2 []byte) bool {
if len(b1) != len(b2) {
return false
}
for i := range b1 {
if b1[i] != b2[i] {
return false
}
}
return true
}
func optimizeArray(arr []any) any {
if len(arr) == 0 {
return arr
}
// Check all strings
if allType[string](arr) {
result := make([]string, len(arr))
for i, v := range arr {
result[i] = v.(string)
}
return result
}
// Check all ints
if allType[int](arr) {
result := make([]int, len(arr))
for i, v := range arr {
result[i] = v.(int)
}
return result
}
return arr
}
func allType[T any](arr []any) bool {
for _, v := range arr {
if _, ok := v.(T); !ok {
return false
}
}
return true
}