package toml import ( "fmt" "reflect" "strconv" ) // Enforce 64-bit platform; fails compilation on 32-bit targets where int(int64) in parseInteger would truncate const _ uint = 1<<63 - 1 // Parser parses TOML tokens into a map[string]any type Parser struct { root map[string]any lexer *Lexer current any // Pointer to the current map or slice of maps being populated (scope) // Identity set of inline-table maps (immutable per TOML spec) frozen map[uintptr]bool curToken Token peekToken Token // Value nesting depth (arrays / inline tables) depth int } // Recursion bound for parseValue -> parseArray/parseInlineTable const maxValueDepth = 1000 func NewParser(input []byte) *Parser { l := NewLexer(input) p := &Parser{ lexer: l, root: make(map[string]any), frozen: make(map[uintptr]bool), } p.nextToken() p.nextToken() p.current = p.root return p } func (p *Parser) nextToken() { p.curToken = p.peekToken p.peekToken = p.lexer.NextToken() // Skip comments automatically for p.peekToken.Type == TokenComment { p.peekToken = p.lexer.NextToken() } } func (p *Parser) Parse() (map[string]any, error) { for p.curToken.Type != TokenEOF { if p.curToken.Type == TokenNewline { p.nextToken() continue } if err := p.parseStatement(); err != nil { return nil, err } } return p.root, nil } func (p *Parser) parseStatement() error { switch p.curToken.Type { case TokenLBracket: // Table Definition: [table] or [[array.table]] return p.parseTableDeclaration() case TokenIdent, TokenString: // Key-Value Pair: key = value return p.parseKeyValuePair(p.current) case TokenError: return fmt.Errorf("lexing error line %d: %s", p.curToken.Line, p.curToken.Literal) default: return fmt.Errorf("unexpected token line %d: %s", p.curToken.Line, p.curToken.String()) } } // parseTableDeclaration handles [key] and [[key]] func (p *Parser) parseTableDeclaration() error { isArray := false if p.peekToken.Type == TokenLBracket { // It is [[ ... p.nextToken() // consume first [ isArray = true } p.nextToken() // consume [ // Parse Key (dotted) keys, err := p.parseKeyParts() if err != nil { return err } if isArray { if p.curToken.Type != TokenRBracket { return fmt.Errorf("expected closing bracket for array table at line %d", p.curToken.Line) } p.nextToken() // consume first ] } if p.curToken.Type != TokenRBracket { return fmt.Errorf("expected closing bracket for table at line %d", p.curToken.Line) } p.nextToken() // consume final ] // Define scope return p.setTableScope(keys, isArray) } // setTableScope navigates/creates the map structure and sets p.current func (p *Parser) setTableScope(keys []string, isArrayOfTables bool) error { // Table declarations always start from root var ptr any = p.root for i, key := range keys { isLast := i == len(keys)-1 currentMap, ok := ptr.(map[string]any) if !ok { return fmt.Errorf("key path conflict: %s is not a map", key) } if isLast { if isArrayOfTables { // [[a.b]] -> Ensure 'b' is a slice of maps, append new map, set cursor to it var slice []map[string]any if val, exists := currentMap[key]; exists { if s, ok := val.([]map[string]any); ok { slice = s } else { return fmt.Errorf("key conflict: %s is not an array of tables", key) } } else { slice = make([]map[string]any, 0) } newMap := make(map[string]any) slice = append(slice, newMap) currentMap[key] = slice p.current = newMap } else { // [a.b] -> Ensure 'b' is a map, set cursor to it var targetMap map[string]any if val, exists := currentMap[key]; exists { if m, ok := val.(map[string]any); ok { // Inline tables cannot be reopened if p.frozen[reflect.ValueOf(m).Pointer()] { return fmt.Errorf("cannot extend inline table %q at line %d", key, p.curToken.Line) } targetMap = m } else { return fmt.Errorf("key conflict: %s is not a table", key) } } else { targetMap = make(map[string]any) currentMap[key] = targetMap } p.current = targetMap } } else { // Intermediate key -> ensure map exists and traverse. // Traversal through an existing [[array]] descends into its last element. if val, exists := currentMap[key]; exists { if m, ok := val.(map[string]any); ok { // Inline tables cannot be extended via sub-tables if p.frozen[reflect.ValueOf(m).Pointer()] { return fmt.Errorf("cannot extend inline table %q at line %d", key, p.curToken.Line) } ptr = m } else if slice, ok := val.([]map[string]any); ok { if len(slice) == 0 { return fmt.Errorf("cannot traverse empty array table %s", key) } ptr = slice[len(slice)-1] } else { return fmt.Errorf("intermediate key %s is not a map", key) } } else { newMap := make(map[string]any) currentMap[key] = newMap ptr = newMap } } } return nil } func (p *Parser) parseKeyValuePair(scope any) error { // Parse Key (dotted allowed: a.b.c = 1) keys, err := p.parseKeyParts() if err != nil { return err } if p.curToken.Type != TokenEqual { return fmt.Errorf("expected '=' after key at line %d, got %s", p.curToken.Line, p.curToken.String()) } p.nextToken() // consume = val, err := p.parseValue() if err != nil { return err } // Assign value to scope return p.assignValue(scope, keys, val) } func (p *Parser) assignValue(scope any, keys []string, val any) error { ptr := scope // If scope is map, easy. If scope is not map, error. currentMap, ok := ptr.(map[string]any) if !ok { return fmt.Errorf("scope is not a map") } for i, key := range keys { if i == len(keys)-1 { // Final key, assign value if _, exists := currentMap[key]; exists { return fmt.Errorf("duplicate key %s at line %d", key, p.curToken.Line) } currentMap[key] = val } else { // Intermediate, ensure map if existing, exists := currentMap[key]; exists { if m, ok := existing.(map[string]any); ok { if p.frozen[reflect.ValueOf(m).Pointer()] { return fmt.Errorf("cannot extend inline table %q at line %d", key, p.curToken.Line) } currentMap = m } else { return fmt.Errorf("intermediate key %s is not a map", key) } } else { newMap := make(map[string]any) currentMap[key] = newMap currentMap = newMap } } } return nil } func (p *Parser) parseKeyParts() ([]string, error) { var keys []string for { // Rule: Tokens identified as Numbers are forbidden as keys if p.curToken.Type == TokenInteger || p.curToken.Type == TokenFloat { return nil, fmt.Errorf("numeric keys are forbidden: %q", p.curToken.Literal) } if p.curToken.Type == TokenString { // Rule: Even quoted strings shouldn't be pure numbers per instruction if _, err := strconv.Atoi(p.curToken.Literal); err == nil { return nil, fmt.Errorf("numeric string keys are forbidden: %q", p.curToken.Literal) } } if p.curToken.Type != TokenIdent && p.curToken.Type != TokenString { return nil, fmt.Errorf("expected key, got %s", p.curToken.String()) } keys = append(keys, p.curToken.Literal) p.nextToken() if p.curToken.Type == TokenDot { p.nextToken() continue } break } return keys, nil } func (p *Parser) parseValue() (any, error) { // Guard unbounded recursion on nested p.depth++ defer func() { p.depth-- }() if p.depth > maxValueDepth { return nil, fmt.Errorf("value nesting exceeds %d at line %d", maxValueDepth, p.curToken.Line) } switch p.curToken.Type { case TokenString: val := p.curToken.Literal p.nextToken() return val, nil case TokenInteger: val, err := p.parseInteger(p.curToken.Literal) if err != nil { return nil, fmt.Errorf("invalid integer %q at line %d: %w", p.curToken.Literal, p.curToken.Line, err) } p.nextToken() return val, nil case TokenFloat: val, err := strconv.ParseFloat(p.curToken.Literal, 64) if err != nil { return nil, fmt.Errorf("invalid float %q at line %d: %w", p.curToken.Literal, p.curToken.Line, err) } p.nextToken() return val, nil case TokenBool: val := p.curToken.Literal == "true" p.nextToken() return val, nil case TokenLBracket: return p.parseArray() case TokenLBrace: return p.parseInlineTable() } return nil, fmt.Errorf("unexpected value token %s at line %d", p.curToken.String(), p.curToken.Line) } func (p *Parser) parseInteger(lit string) (int, error) { // Handle optional leading sign negative := false numLit := lit if len(numLit) > 0 && (numLit[0] == '+' || numLit[0] == '-') { negative = numLit[0] == '-' numLit = numLit[1:] } var val int64 var err error if len(numLit) > 2 && numLit[0] == '0' { switch numLit[1] { case 'x', 'X': val, err = strconv.ParseInt(numLit[2:], 16, 64) case 'o', 'O': val, err = strconv.ParseInt(numLit[2:], 8, 64) case 'b', 'B': val, err = strconv.ParseInt(numLit[2:], 2, 64) default: val, err = strconv.ParseInt(lit, 10, 64) return int(val), err } if err != nil { return 0, err } if negative { val = -val } return int(val), nil } val, err = strconv.ParseInt(lit, 10, 64) return int(val), err } func (p *Parser) parseArray() ([]any, error) { p.nextToken() // consume [ arr := make([]any, 0) for p.curToken.Type != TokenRBracket { if p.curToken.Type == TokenNewline { p.nextToken() continue } val, err := p.parseValue() if err != nil { return nil, err } arr = append(arr, val) if p.curToken.Type == TokenComma { p.nextToken() } else if p.curToken.Type != TokenRBracket { // Check for newlines between elements if missing comma? TOML usually requires comma. // Relaxed parser: require comma unless followed immediately by bracket (trailing comma allowed) if p.curToken.Type == TokenNewline { p.nextToken() continue } return nil, fmt.Errorf("expected comma or closing bracket in array at line %d", p.curToken.Line) } } p.nextToken() // consume ] return arr, nil } func (p *Parser) parseInlineTable() (map[string]any, error) { p.nextToken() // consume { m := make(map[string]any) for p.curToken.Type != TokenRBrace { if p.curToken.Type == TokenNewline { p.nextToken() continue } // Parse key = value keys, err := p.parseKeyParts() if err != nil { return nil, err } if p.curToken.Type != TokenEqual { return nil, fmt.Errorf("expected '=' in inline table at line %d", p.curToken.Line) } p.nextToken() val, err := p.parseValue() if err != nil { return nil, err } // Inline tables can have dotted keys too: { a.b = 1 } if err := p.assignValue(m, keys, val); err != nil { return nil, err } if p.curToken.Type == TokenComma { p.nextToken() } else if p.curToken.Type != TokenRBrace { if p.curToken.Type == TokenNewline { p.nextToken() continue } return nil, fmt.Errorf("expected comma or closing brace in inline table at line %d", p.curToken.Line) } } p.nextToken() // consume } // Mark inline table immutable. Value.Pointer for maps is documented // stable for identity comparison. Nested inline tables self-mark on return; // same-table dotted assignments happen before the mark, so intra-table // dotted keys ({a.b = 1}) remain unaffected. p.frozen[reflect.ValueOf(m).Pointer()] = true return m, nil }