Support single quoted keys (#193)

Fixes #61
This commit is contained in:
Kazuyoshi Kato
2017-10-19 07:56:30 -07:00
committed by Thomas Pelletier
parent 878c11e70e
commit a410399d2c
2 changed files with 103 additions and 22 deletions
+88 -21
View File
@@ -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
View File
@@ -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) {