162 lines
3.0 KiB
Go
162 lines
3.0 KiB
Go
|
// 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)
|
||
|
}
|