diff --git a/config.go b/config.go index ba8a19b..a19ff50 100644 --- a/config.go +++ b/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 - return result, 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 + } + + // 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 based on token type + // 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 + 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, + } + stack = append(stack, newState) + } else { + // Handle primitive value + value := c.tokenToValue(valueToken) + current.object[name] = value + } } else if nextToken.Type == TokenOpenBrace { - // It's a nested object - objValue, err := c.parseObject() - if err != nil { - return nil, err - } + // Nested object with name + current.isArray = false + current.currentKey = name + current.expectValue = true - isArray = false - contents[name] = objValue + // 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 { - // Put the token back and treat the name as an array element + // 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 +}