Fin/parser.go
2025-03-01 21:10:04 -06:00

172 lines
3.7 KiB
Go

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')
}