committed by
Thomas Pelletier
parent
878c11e70e
commit
a410399d2c
+88
-21
@@ -6,15 +6,37 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"unicode"
|
"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) {
|
func parseKey(key string) ([]string, error) {
|
||||||
groups := []string{}
|
groups := []string{}
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
inQuotes := false
|
var hex bytes.Buffer
|
||||||
|
state := BARE
|
||||||
wasInQuotes := false
|
wasInQuotes := false
|
||||||
escapeNext := false
|
|
||||||
ignoreSpace := true
|
ignoreSpace := true
|
||||||
expectDot := false
|
expectDot := false
|
||||||
|
|
||||||
@@ -25,25 +47,67 @@ func parseKey(key string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
ignoreSpace = false
|
ignoreSpace = false
|
||||||
}
|
}
|
||||||
if escapeNext {
|
|
||||||
buffer.WriteRune(char)
|
if state == ESC {
|
||||||
escapeNext = false
|
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
|
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 {
|
switch char {
|
||||||
case '\\':
|
case '\\':
|
||||||
escapeNext = true
|
if state == BASIC {
|
||||||
continue
|
state = ESC
|
||||||
case '"':
|
} else if state == LITERAL {
|
||||||
if inQuotes {
|
buffer.WriteRune(char)
|
||||||
|
}
|
||||||
|
case '\'':
|
||||||
|
if state == BARE {
|
||||||
|
state = LITERAL
|
||||||
|
} else if state == LITERAL {
|
||||||
groups = append(groups, buffer.String())
|
groups = append(groups, buffer.String())
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
wasInQuotes = true
|
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
|
||||||
}
|
}
|
||||||
inQuotes = !inQuotes
|
|
||||||
expectDot = false
|
expectDot = false
|
||||||
case '.':
|
case '.':
|
||||||
if inQuotes {
|
if state != BARE {
|
||||||
buffer.WriteRune(char)
|
buffer.WriteRune(char)
|
||||||
} else {
|
} else {
|
||||||
if !wasInQuotes {
|
if !wasInQuotes {
|
||||||
@@ -58,28 +122,31 @@ func parseKey(key string) ([]string, error) {
|
|||||||
wasInQuotes = false
|
wasInQuotes = false
|
||||||
}
|
}
|
||||||
case ' ':
|
case ' ':
|
||||||
if inQuotes {
|
if state == BASIC {
|
||||||
buffer.WriteRune(char)
|
buffer.WriteRune(char)
|
||||||
} else {
|
} else {
|
||||||
expectDot = true
|
expectDot = true
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if !inQuotes && !isValidBareChar(char) {
|
if state == BARE {
|
||||||
return nil, fmt.Errorf("invalid bare character: %c", char)
|
if !isValidBareChar(char) {
|
||||||
}
|
return nil, fmt.Errorf("invalid bare character: %c", char)
|
||||||
if !inQuotes && expectDot {
|
} else if expectDot {
|
||||||
return nil, errors.New("what?")
|
return nil, errors.New("what?")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
buffer.WriteRune(char)
|
buffer.WriteRune(char)
|
||||||
expectDot = false
|
expectDot = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if inQuotes {
|
|
||||||
|
// 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")
|
return nil, errors.New("mismatched quotes")
|
||||||
}
|
}
|
||||||
if escapeNext {
|
|
||||||
return nil, errors.New("unfinished escape sequence")
|
|
||||||
}
|
|
||||||
if buffer.Len() > 0 {
|
if buffer.Len() > 0 {
|
||||||
groups = append(groups, buffer.String())
|
groups = append(groups, buffer.String())
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -22,7 +22,10 @@ func testResult(t *testing.T, key string, expected []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testError(t *testing.T, key string, expectedError string) {
|
func testError(t *testing.T, key string, expectedError string) {
|
||||||
_, err := parseKey(key)
|
res, err := parseKey(key)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error, but succesfully parsed key %s", res)
|
||||||
|
}
|
||||||
if fmt.Sprintf("%s", err) != expectedError {
|
if fmt.Sprintf("%s", err) != expectedError {
|
||||||
t.Fatalf("Expected error \"%s\", but got \"%s\".", expectedError, err)
|
t.Fatalf("Expected error \"%s\", but got \"%s\".", expectedError, err)
|
||||||
}
|
}
|
||||||
@@ -47,6 +50,17 @@ func TestBaseKeyPound(t *testing.T) {
|
|||||||
func TestQuotedKeys(t *testing.T) {
|
func TestQuotedKeys(t *testing.T) {
|
||||||
testResult(t, `hello."foo".bar`, []string{"hello", "foo", "bar"})
|
testResult(t, `hello."foo".bar`, []string{"hello", "foo", "bar"})
|
||||||
testResult(t, `"hello!"`, []string{"hello!"})
|
testResult(t, `"hello!"`, []string{"hello!"})
|
||||||
|
testResult(t, `"hello\tworld"`, []string{"hello\tworld"})
|
||||||
|
testResult(t, `"\U0001F914"`, []string{"\U0001F914"})
|
||||||
|
testResult(t, `"\u2764"`, []string{"\u2764"})
|
||||||
|
|
||||||
|
testResult(t, `hello.'foo'.bar`, []string{"hello", "foo", "bar"})
|
||||||
|
testResult(t, `'hello!'`, []string{"hello!"})
|
||||||
|
testResult(t, `'hello\tworld'`, []string{`hello\tworld`})
|
||||||
|
|
||||||
|
testError(t, `"\w"`, `invalid escape sequence \w`)
|
||||||
|
testError(t, `"\`, `unfinished escape sequence`)
|
||||||
|
testError(t, `"\t`, `mismatched quotes`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyKey(t *testing.T) {
|
func TestEmptyKey(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user