ref 7
This commit is contained in:
parent
8258a967a2
commit
bb3478eb47
210
config.go
210
config.go
|
@ -4,9 +4,17 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ParseState represents a parsing level state
|
||||
type ParseState struct {
|
||||
object map[string]any
|
||||
arrayElements []any
|
||||
isArray bool
|
||||
currentKey string
|
||||
expectValue bool
|
||||
}
|
||||
|
||||
// Config holds a single hierarchical structure like JSON and handles parsing
|
||||
type Config struct {
|
||||
data map[string]any
|
||||
|
@ -310,13 +318,6 @@ func (c *Config) parseContent() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// valuePool to reuse maps and slices for common value types
|
||||
var valuePool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make(map[string]any, 8)
|
||||
},
|
||||
}
|
||||
|
||||
// parseValue parses a value after an equals sign
|
||||
func (c *Config) parseValue() (any, error) {
|
||||
token, err := c.nextToken()
|
||||
|
@ -366,129 +367,134 @@ func (c *Config) parseValue() (any, error) {
|
|||
|
||||
// parseObject parses a map or array
|
||||
func (c *Config) parseObject() (any, error) {
|
||||
// Get a map from the pool
|
||||
contents := valuePool.Get().(map[string]any)
|
||||
// Clear the map to reuse it
|
||||
for k := range contents {
|
||||
delete(contents, k)
|
||||
}
|
||||
// Initialize stack with first state
|
||||
stack := []*ParseState{{
|
||||
object: make(map[string]any, 8),
|
||||
arrayElements: make([]any, 0, 8),
|
||||
isArray: true,
|
||||
}}
|
||||
|
||||
// Ensure map is returned to pool on function exit
|
||||
defer func() {
|
||||
// Only return to pool if we're using array (contents becomes unused)
|
||||
// If we're returning contents directly, don't return to pool
|
||||
if contents != nil {
|
||||
valuePool.Put(contents)
|
||||
}
|
||||
}()
|
||||
for len(stack) > 0 {
|
||||
// Get current state from top of stack
|
||||
current := stack[len(stack)-1]
|
||||
|
||||
// Use pre-allocated capacity for array elements to avoid reallocations
|
||||
arrayElements := make([]any, 0, 8)
|
||||
isArray := true
|
||||
|
||||
for {
|
||||
token, err := c.nextToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for end of object
|
||||
// Handle closing brace - finish current object/array
|
||||
if token.Type == TokenCloseBrace {
|
||||
if isArray && len(contents) == 0 {
|
||||
// Using array, set contents to nil to signal in defer that it should be returned to pool
|
||||
contentsToReturn := contents
|
||||
contents = nil
|
||||
valuePool.Put(contentsToReturn)
|
||||
return arrayElements, nil
|
||||
// Determine result based on what we've collected
|
||||
var result any
|
||||
if current.isArray && len(current.object) == 0 {
|
||||
result = current.arrayElements
|
||||
} else {
|
||||
result = current.object
|
||||
}
|
||||
|
||||
// We're returning contents directly, set to nil to signal in defer not to return to pool
|
||||
result := contents
|
||||
contents = nil
|
||||
// Pop the stack
|
||||
stack = stack[:len(stack)-1]
|
||||
|
||||
// If stack is empty, we're done with the root object
|
||||
if len(stack) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Handle based on token type
|
||||
// Otherwise, add result to parent
|
||||
parent := stack[len(stack)-1]
|
||||
if parent.expectValue {
|
||||
parent.object[parent.currentKey] = result
|
||||
parent.expectValue = false
|
||||
} else {
|
||||
parent.arrayElements = append(parent.arrayElements, result)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle tokens based on type
|
||||
switch token.Type {
|
||||
case TokenName:
|
||||
// Get the name value - must copy for stability
|
||||
name := string(token.Value)
|
||||
|
||||
// Get the next token to determine if it's a map entry or array element
|
||||
// Look ahead to determine context
|
||||
nextToken, err := c.nextToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nextToken.Type == TokenEquals {
|
||||
// It's a key-value pair
|
||||
value, err := c.parseValue()
|
||||
// Key-value pair
|
||||
current.isArray = false
|
||||
current.currentKey = name
|
||||
current.expectValue = true
|
||||
|
||||
// Parse the value
|
||||
valueToken, err := c.nextToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isArray = false
|
||||
contents[name] = value
|
||||
} else if nextToken.Type == TokenOpenBrace {
|
||||
// It's a nested object
|
||||
objValue, err := c.parseObject()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if valueToken.Type == TokenOpenBrace {
|
||||
// Push new state for nested object/array
|
||||
newState := &ParseState{
|
||||
object: make(map[string]any, 8),
|
||||
arrayElements: make([]any, 0, 8),
|
||||
isArray: true,
|
||||
}
|
||||
|
||||
isArray = false
|
||||
contents[name] = objValue
|
||||
stack = append(stack, newState)
|
||||
} else {
|
||||
// Put the token back and treat the name as an array element
|
||||
// Handle primitive value
|
||||
value := c.tokenToValue(valueToken)
|
||||
current.object[name] = value
|
||||
}
|
||||
} else if nextToken.Type == TokenOpenBrace {
|
||||
// Nested object with name
|
||||
current.isArray = false
|
||||
current.currentKey = name
|
||||
current.expectValue = true
|
||||
|
||||
// Push new state for nested object
|
||||
newState := &ParseState{
|
||||
object: make(map[string]any, 8),
|
||||
arrayElements: make([]any, 0, 8),
|
||||
isArray: true,
|
||||
}
|
||||
stack = append(stack, newState)
|
||||
} else {
|
||||
// Array element
|
||||
c.scanner.UnreadToken(nextToken)
|
||||
|
||||
// Convert to appropriate type if possible
|
||||
var value any = name
|
||||
|
||||
// Try to infer type
|
||||
if name == "true" {
|
||||
value = true
|
||||
} else if name == "false" {
|
||||
value = false
|
||||
} else if isDigitOrMinus(name) {
|
||||
// Try to parse as number
|
||||
numValue, err := parseStringAsNumber(name)
|
||||
if err == nil {
|
||||
value = numValue
|
||||
}
|
||||
}
|
||||
|
||||
arrayElements = append(arrayElements, value)
|
||||
// Convert name to appropriate type
|
||||
value := c.convertNameValue(name)
|
||||
current.arrayElements = append(current.arrayElements, value)
|
||||
}
|
||||
|
||||
case TokenString, TokenNumber, TokenBoolean:
|
||||
// Direct array element
|
||||
var value any
|
||||
value := c.tokenToValue(token)
|
||||
|
||||
switch token.Type {
|
||||
case TokenString:
|
||||
value = string(token.Value)
|
||||
case TokenNumber:
|
||||
strVal := string(token.Value)
|
||||
value, _ = parseStringAsNumber(strVal)
|
||||
case TokenBoolean:
|
||||
value = bytesEqual(token.Value, []byte("true"))
|
||||
if current.expectValue {
|
||||
current.object[current.currentKey] = value
|
||||
current.expectValue = false
|
||||
} else {
|
||||
current.arrayElements = append(current.arrayElements, value)
|
||||
}
|
||||
|
||||
arrayElements = append(arrayElements, value)
|
||||
|
||||
case TokenOpenBrace:
|
||||
// Nested object in array
|
||||
nestedObj, err := c.parseObject()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// New nested object/array
|
||||
newState := &ParseState{
|
||||
object: make(map[string]any, 8),
|
||||
arrayElements: make([]any, 0, 8),
|
||||
isArray: true,
|
||||
}
|
||||
arrayElements = append(arrayElements, nestedObj)
|
||||
stack = append(stack, newState)
|
||||
|
||||
default:
|
||||
return nil, c.Error(fmt.Sprintf("unexpected token in object: %v", token.Type))
|
||||
return nil, c.Error(fmt.Sprintf("unexpected token: %v", token.Type))
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unexpected end of parsing")
|
||||
}
|
||||
|
||||
// Load parses a config from a reader
|
||||
|
@ -559,3 +565,31 @@ func parseStringAsNumber(s string) (any, error) {
|
|||
// It's an integer
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
|
||||
func (c *Config) tokenToValue(token Token) any {
|
||||
switch token.Type {
|
||||
case TokenString:
|
||||
return string(token.Value)
|
||||
case TokenNumber:
|
||||
val, _ := parseStringAsNumber(string(token.Value))
|
||||
return val
|
||||
case TokenBoolean:
|
||||
return bytesEqual(token.Value, []byte("true"))
|
||||
default:
|
||||
return string(token.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) convertNameValue(name string) any {
|
||||
if name == "true" {
|
||||
return true
|
||||
} else if name == "false" {
|
||||
return false
|
||||
} else if isDigitOrMinus(name) {
|
||||
val, err := parseStringAsNumber(name)
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user