605 lines
12 KiB
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
|
|
}
|