// Parsing keys handling both bare and quoted keys. package toml import ( "bytes" "errors" "fmt" "strconv" "unicode" ) var escapeSequenceMap = map[rune]rune{ 'b': '\b', 't': '\t', 'n': '\n', 'f': '\f', 'r': '\r', '"': '"', '\\': '\\', } type parseKeyState int const ( BARE parseKeyState = iota BASIC LITERAL ESC UNICODE_4 UNICODE_8 ) func parseKey(key string) ([]string, error) { groups := []string{} var buffer bytes.Buffer var hex bytes.Buffer state := BARE wasInQuotes := false ignoreSpace := true expectDot := false for _, char := range key { if ignoreSpace { if char == ' ' { continue } ignoreSpace = false } if state == ESC { if char == 'u' { state = UNICODE_4 hex.Reset() } else if char == 'U' { state = UNICODE_8 hex.Reset() } else if newChar, ok := escapeSequenceMap[char]; ok { buffer.WriteRune(newChar) state = BASIC } else { return nil, fmt.Errorf(`invalid escape sequence \%c`, char) } continue } if state == UNICODE_4 || state == UNICODE_8 { if isHexDigit(char) { hex.WriteRune(char) } if (state == UNICODE_4 && hex.Len() == 4) || (state == UNICODE_8 && hex.Len() == 8) { if value, err := strconv.ParseInt(hex.String(), 16, 32); err == nil { buffer.WriteRune(rune(value)) } else { return nil, err } state = BASIC } continue } switch char { case '\\': if state == BASIC { state = ESC } else if state == LITERAL { buffer.WriteRune(char) } case '\'': if state == BARE { state = LITERAL } else if state == LITERAL { groups = append(groups, buffer.String()) buffer.Reset() wasInQuotes = true state = BARE } expectDot = false case '"': if state == BARE { state = BASIC } else if state == BASIC { groups = append(groups, buffer.String()) buffer.Reset() state = BARE wasInQuotes = true } expectDot = false case '.': if state != BARE { buffer.WriteRune(char) } else { if !wasInQuotes { if buffer.Len() == 0 { return nil, errors.New("empty table key") } groups = append(groups, buffer.String()) buffer.Reset() } ignoreSpace = true expectDot = false wasInQuotes = false } case ' ': if state == BASIC { buffer.WriteRune(char) } else { expectDot = true } default: if state == BARE { if !isValidBareChar(char) { return nil, fmt.Errorf("invalid bare character: %c", char) } else if expectDot { return nil, errors.New("what?") } } buffer.WriteRune(char) expectDot = false } } // state must be BARE at the end if state == ESC { return nil, errors.New("unfinished escape sequence") } else if state != BARE { return nil, errors.New("mismatched quotes") } if buffer.Len() > 0 { groups = append(groups, buffer.String()) } if len(groups) == 0 { return nil, errors.New("empty key") } return groups, nil } func isValidBareChar(r rune) bool { return isAlphanumeric(r) || r == '-' || unicode.IsNumber(r) }