1// Parsing keys handling both bare and quoted keys.
2
3package toml
4
5import (
6	"errors"
7	"fmt"
8)
9
10// Convert the bare key group string to an array.
11// The input supports double quotation and single quotation,
12// but escape sequences are not supported. Lexers must unescape them beforehand.
13func parseKey(key string) ([]string, error) {
14	runes := []rune(key)
15	var groups []string
16
17	if len(key) == 0 {
18		return nil, errors.New("empty key")
19	}
20
21	idx := 0
22	for idx < len(runes) {
23		for ; idx < len(runes) && isSpace(runes[idx]); idx++ {
24			// skip leading whitespace
25		}
26		if idx >= len(runes) {
27			break
28		}
29		r := runes[idx]
30		if isValidBareChar(r) {
31			// parse bare key
32			startIdx := idx
33			endIdx := -1
34			idx++
35			for idx < len(runes) {
36				r = runes[idx]
37				if isValidBareChar(r) {
38					idx++
39				} else if r == '.' {
40					endIdx = idx
41					break
42				} else if isSpace(r) {
43					endIdx = idx
44					for ; idx < len(runes) && isSpace(runes[idx]); idx++ {
45						// skip trailing whitespace
46					}
47					if idx < len(runes) && runes[idx] != '.' {
48						return nil, fmt.Errorf("invalid key character after whitespace: %c", runes[idx])
49					}
50					break
51				} else {
52					return nil, fmt.Errorf("invalid bare key character: %c", r)
53				}
54			}
55			if endIdx == -1 {
56				endIdx = idx
57			}
58			groups = append(groups, string(runes[startIdx:endIdx]))
59		} else if r == '\'' {
60			// parse single quoted key
61			idx++
62			startIdx := idx
63			for {
64				if idx >= len(runes) {
65					return nil, fmt.Errorf("unclosed single-quoted key")
66				}
67				r = runes[idx]
68				if r == '\'' {
69					groups = append(groups, string(runes[startIdx:idx]))
70					idx++
71					break
72				}
73				idx++
74			}
75		} else if r == '"' {
76			// parse double quoted key
77			idx++
78			startIdx := idx
79			for {
80				if idx >= len(runes) {
81					return nil, fmt.Errorf("unclosed double-quoted key")
82				}
83				r = runes[idx]
84				if r == '"' {
85					groups = append(groups, string(runes[startIdx:idx]))
86					idx++
87					break
88				}
89				idx++
90			}
91		} else if r == '.' {
92			idx++
93			if idx >= len(runes) {
94				return nil, fmt.Errorf("unexpected end of key")
95			}
96			r = runes[idx]
97			if !isValidBareChar(r) && r != '\'' && r != '"' && r != ' ' {
98				return nil, fmt.Errorf("expecting key part after dot")
99			}
100		} else {
101			return nil, fmt.Errorf("invalid key character: %c", r)
102		}
103	}
104	if len(groups) == 0 {
105		return nil, fmt.Errorf("empty key")
106	}
107	return groups, nil
108}
109
110func isValidBareChar(r rune) bool {
111	return isAlphanumeric(r) || r == '-' || isDigit(r)
112}
113