package config import ( "fmt" "io" ) // Parser parses configuration files type Parser struct { scanner *Scanner config *Config currentObject map[string]any stack []map[string]any } // NewParser creates a new parser with a reader and empty config func NewParser(r io.Reader) *Parser { config := NewConfig() return &Parser{ scanner: NewScanner(r), config: config, currentObject: config.data, stack: make([]map[string]any, 0, 8), // Pre-allocate stack with reasonable capacity } } // Error creates an error with line information from the scanner func (p *Parser) Error(msg string) error { return fmt.Errorf("line %d: %s", p.scanner.line, msg) } // Parse parses the config file and returns a Config func (p *Parser) Parse() (*Config, error) { err := p.parseContent() if err != nil { return nil, err } return p.config, nil } // pushObject enters a new object scope func (p *Parser) pushObject(obj map[string]any) { p.stack = append(p.stack, p.currentObject) p.currentObject = obj } // popObject exits the current object scope func (p *Parser) popObject() { n := len(p.stack) if n > 0 { p.currentObject = p.stack[n-1] p.stack = p.stack[:n-1] } } // parseContent is the main parsing function func (p *Parser) parseContent() error { skipErr := p.scanner.SkipWhitespace() for ; skipErr == nil; skipErr = p.scanner.SkipWhitespace() { r, peekErr := p.scanner.PeekRune() if peekErr == io.EOF { break } if peekErr != nil { return peekErr } // Handle comments if r == '-' { if err := p.scanner.ScanComment(); err != nil { return err } continue } // Handle name=value pairs or named objects if isLetter(r) { name, err := p.scanner.ScanName() if err != nil { return err } if err = p.scanner.SkipWhitespace(); err != nil { return err } r, err = p.scanner.PeekRune() if err != nil && err != io.EOF { return err } // Assignment or direct map/array if r == '=' { // It's a standard key=value pair p.scanner.ReadRune() // consume '=' if err = p.scanner.SkipWhitespace(); err != nil { return err } value, err := p.scanner.ScanValue() if err != nil { return err } // Store the value directly if mapValue, ok := value.(map[string]any); ok { // Add an entry in current object newMap := make(map[string]any, 8) // Pre-allocate with capacity p.currentObject[name] = newMap // Process the map contents p.pushObject(newMap) // Copy values from scanned map to our object for k, v := range mapValue { p.currentObject[k] = v } p.popObject() } else { // Direct storage for primitives and arrays p.currentObject[name] = value } } else if r == '{' { // It's a map/array without '=' value, err := p.scanner.ScanValue() if err != nil { return err } // Store the complex value directly if mapValue, ok := value.(map[string]any); ok { // Add an entry in current object newMap := make(map[string]any, 8) // Pre-allocate with capacity p.currentObject[name] = newMap // Process the map contents p.pushObject(newMap) // Copy values from scanned map to our object for k, v := range mapValue { p.currentObject[k] = v } p.popObject() } else { // Direct storage for arrays p.currentObject[name] = value } } else { return p.Error("expected '=' or '{' after name") } continue } return p.Error("unexpected character") } if skipErr != nil && skipErr != io.EOF { return skipErr } return nil } // Helper function func isLetter(r rune) bool { return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') }